렉시컬 스코프 v 렉시컬 환경
작성일:2025.02.13|조회수:0

렉시컬 스코프라는 용어를 처음 접했을 때, 나는 이것을 별다른 의심 없이 ‘렉시컬 환경에 따른 스코프 체인’ 정도로 이해했다. 두 개념이 밀접하게 연결되어 있다는 인상 때문이었을 것이다. 그 결과, 모 회사의 면접에서 렉시컬 스코프를 설명해 보라는 질문을 받았을 때 스코프 체인에 대한 설명만을 늘어놓고 말았다.
면접이 끝난 뒤, 렉시컬 스코프와 스코프 체인은 다른 개념이라는 피드백을 들었다. 하지만 왜 다른지, 무엇이 다른지, 어떤 관점에서 구분해야 하는지는 명확히 설명할 수 없었다. 이후 코어 자바스크립트와 YDKJSY를 차례로 읽으며, 이 두 개념이 비슷해 보일 뿐 실제로는 서로 다른 레이어의 개념이라는 사실을 비로소 이해하게 되었다.
렉시컬 스코프는 변수와 함수의 유효 범위가 어디에서 선언되었는지에 의해 결정된다는 원칙을 의미한다. 이는 정적으로 결정되는 규칙이며, 코드가 작성된 위치에 의해 스코프가 고정된다. 다시 말해, 함수가 어느 코드 블록 안에서 선언되었는지가 그 함수가 접근할 수 있는 변수의 범위를 결정하며, 이 결정은 실행 시점의 호출 방식이나 실행 흐름에 의해 바뀌지 않는다.
이러한 이유로 렉시컬 스코프는 흔히 컴파일 타임에 결정된다고 설명된다. 실제로 자바스크립트 엔진은 코드를 실행하기 전에 이미 변수 접근 가능 여부를 구조적으로 확정해 두며, 이후 실행 과정에서는 이 규칙을 변경하지 않는다.
반면 렉시컬 환경은 변수와 함수가 실제로 어떤 값을 가지고 있는지를 저장하고 관리하는 실행 시점의 구조이다. 렉시컬 환경은 환경 레코드(Environment Record)를 통해 해당 스코프 내의 식별자와 그에 대응하는 값을 관리한다. 여기에는 변수 선언, 함수 선언, 매개변수, 그리고 this 바인딩과 같은 정보가 포함된다.
즉, 렉시컬 스코프가 “어디까지 접근할 수 있는가”를 정의하는 규칙이라면, 렉시컬 환경은 “그 접근 가능한 식별자들이 현재 어떤 상태를 가지고 있는가”를 관리하는 구체적인 메커니즘이다. 이 관점에서 렉시컬 환경은 렉시컬 스코프가 런타임에 실체화된 결과물이라고 볼 수 있다.
렉시컬 환경은 실행 컨텍스트가 생성될 때마다 동적으로 만들어진다. 각 실행 컨텍스트는 자신만의 렉시컬 환경을 가지며, 동시에 외부 렉시컬 환경에 대한 참조를 유지한다. 이 참조의 연결 구조가 바로 스코프 체인이다. 스코프 체인은 현재 실행 중인 코드가 어떤 렉시컬 환경들에 접근할 수 있는지를 선형적으로 연결한 구조이며, 변수 조회 시 이 체인을 따라 상위 환경으로 탐색을 진행한다. 따라서 스코프 체인은 렉시컬 스코프라는 규칙을 기반으로 하되, 실제 동작은 렉시컬 환경 간의 참조 관계를 통해 이루어진다.
이러한 맥락에서 클로저 역시 렉시컬 스코프 자체보다는 렉시컬 환경에 더 직접적으로 의존한다. 함수가 선언될 당시의 외부 렉시컬 환경에 대한 참조가 유지되기 때문에, 함수 실행이 끝난 이후에도 해당 환경의 값에 접근할 수 있는 것이다. 이는 “스코프 규칙”이 아니라 “환경이 유지되는 방식”의 문제에 가깝다.
정리하면 다음과 같다. 렉시컬 스코프는 컴파일 타임에 결정되는 변수 유효 범위에 대한 규칙이며, 렉시컬 환경은 그 규칙을 바탕으로 자바스크립트 엔진이 런타임에 변수와 값을 관리하는 실제 구조이다. 스코프 체인과 클로저는 렉시컬 스코프 위에 존재하지만, 그 동작의 중심에는 항상 렉시컬 환경이 있다.
더 읽어보기
2026.03.13
Streams API 3. 바이트 스트림과 실전 파이프라인
앞선 두 글에서 우리는 Web Streams API의 표면과 의미론을 정리했다. 이제 남은 질문은 하나이다. 이 지식을 실제 어디에 써먹을 것인가. 이 질문에 답하는 순간 스트림 학습은 비로소 완성된다. 표준을 읽고 메서드를 아는 것만으로는 충분하지 않다. fetch() 응답 본문을 어…
2026.03.13
Streams API 2. 상태와 백프레셔의 의미론
스트림을 처음 배울 때는 읽고 쓰는 예제가 꽤 단순해 보인다. getReader()로 읽고, getWriter()로 쓰고, pipeTo()로 연결하면 끝나는 것처럼 느껴진다. 실제로 짧은 데모는 이 정도만 알아도 돌아간다. 그러나 실무에서 스트림 코드를 망가뜨리는 원인은 거의 언제나 더…
2026.03.13
Streams API 1. 읽기와 쓰기의 출발점
자바스크립트에서 비동기를 배울 때 우리는 대개 Promise와 async, await부터 익힌다. 이 조합은 한 번의 결과를 기다리는 문제에는 매우 강력하다. 그러나 데이터가 한 번에 끝나지 않고 계속 흘러들어오는 상황에서는 이야기가 달라진다. 네트워크 응답이 길게 이어지거나, 큰 파일…
2026.03.04
JavaScript를 위한 더 나은 Streams API가 필요하다
이 포스트는 node.js의 코어 컨트리뷰트이며 Cloudflare Workers 팀 소속 개발자 James M Snell이 cloudflare 블로그에 올린 We deserve a better streams API for JavaScript 게시글을 번역한 것이다. 번역하는 과정에서…
2026.04.11
Trie 자료구조
문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…
2026.03.19
Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까
웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…
2026.03.19
Streams API 부록 1. HTTP 다운로드 진행률은 어떻게 계산될까
파일을 다운로드할 때 가끔 몇 퍼센트 진행되었는지 혹은 진행 막대(progress bar)가 조금씩 채워지는 모습을 볼 수 있다. 그런데 모든 다운로드가 이런 식으로 진행률을 보여 주는 것은 아니다. 어떤 다운로드는 퍼센트가 표시되지만, 어떤 경우에는 진행 막대 없이 로딩 스피너만 계속…
2026.03.13
Streams API 4. 왜 모든 언어에는 Stream API가 존재할까
Streams API를 공부하다 보면 묘한 기시감을 느끼게 된다. JavaScript에서 ReadableStream, WritableStream, TransformStream을 살펴보고 있는데, 어딘가 낯설지 않다. Java를 써 본 사람이라면 InputStream, OutputStre…
댓글
댓글을 불러오는 중...