[JavaScript] 렉시컬 스코프(Lexical Scope)란?
렉시컬 스코프(Lexical Scope)란 코드가 “어디에 작성되었는지(선언 위치)”에 따라 스코프(식별자 접근 범위)가 결정되는 규칙을 의미한다.
다른 표현으로는 정적 스코프(Static Scope) 라고도 한다.
자바스크립트를 포함한 많은 언어에서 이 규칙을 채택하며, 특히 클로저(Closure) 를 이해하기 위해 반드시 짚고 넘어가야 하는 핵심 개념이다.
스코프란 무엇인가
스코프(scope)는 변수를 포함한 식별자(identifier)가 유효하게 참조될 수 있는 범위를 뜻한다.
즉, 어떤 변수를 “어디에서 쓸 수 있는가”를 정하는 규칙이며, 스코프가 존재하는 이유는 다음과 같다.
- 이름 충돌을 방지한다.
- 코드의 구조를 명확히 하고 유지보수를 쉽게 한다.
- 의도하지 않은 접근을 제한하여 안정성을 높인다.
렉시컬 스코프의 핵심
렉시컬 스코프의 핵심은 단순하다.
함수가 호출된 위치가 아니라, 함수가 선언된 위치를 기준으로 상위 스코프가 결정된다.
즉, 실행 시점(runtime)에 “어디서 호출했는가”가 아니라, 작성 시점(compile-time 혹은 파싱 단계)에 “어디에 선언했는가”가 중요하다.
예제로 이해하기
다음 예제를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const x = "global";
function outer() {
const x = "outer";
function inner() {
console.log(x);
}
return inner;
}
const fn = outer();
fn(); // "outer"
위 코드에서 inner 함수는 outer 함수 안에서 선언되었다.
따라서 inner가 참조하는 x는 전역의 x가 아니라, 선언 위치 기준으로 가장 가까운 상위 스코프인 outer의 x가 된다.
여기서 중요한 점은 fn()이 어디에서 호출되든 결과가 변하지 않는다는 것이다.
호출 위치가 아니라 선언 위치가 스코프를 결정하기 때문이다.
호출 위치가 기준이라면? (동적 스코프와 비교)
렉시컬 스코프와 대비되는 개념으로 동적 스코프(Dynamic Scope) 가 있다.
동적 스코프는 “호출한 위치”를 기준으로 상위 스코프를 탐색한다.
자바스크립트는 동적 스코프를 채택하지 않는다.
따라서 아래 예제에서도 호출 위치로 인해 값이 바뀌지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
const x = "global";
function printX() {
console.log(x);
}
function runner() {
const x = "runner";
printX();
}
runner(); // "global"
printX()는 전역에서 선언되었기 때문에, runner() 내부에서 호출되더라도 runner의 x를 보지 않는다.
이는 렉시컬 스코프 규칙을 그대로 따르는 결과이다.
스코프 체인과 렉시컬 환경
자바스크립트 엔진은 식별자를 찾을 때 다음 순서로 탐색한다.
1) 현재 스코프에서 찾는다.
2) 없으면 상위 스코프로 올라간다.
3) 최상위(전역)까지 반복한다.
4) 끝까지 없으면 ReferenceError가 발생한다.
이 탐색 구조를 흔히 스코프 체인(Scope Chain) 이라고 부른다.
그리고 이러한 탐색을 가능하게 하는 내부 구조를 렉시컬 환경(Lexical Environment) 라고 설명한다.
렉시컬 스코프와 클로저의 관계
클로저는 함수가 자신이 선언될 당시의 상위 스코프를 기억하고 접근할 수 있는 특성을 의미한다.
이 문장 자체가 사실상 렉시컬 스코프를 전제로 한다.
즉,
렉시컬 스코프: 상위 스코프는 선언 위치로 결정된다.
클로저: 그 상위 스코프에 대한 접근을 함수가 유지한다.
따라서 렉시컬 스코프를 이해하지 못하면 클로저는 단순 암기 수준에 머무르게 된다.
마무리
렉시컬 스코프란 함수와 변수의 접근 범위가 “코드에 작성된 위치(선언 위치)”를 기준으로 결정되는 규칙이다.