스코프란 식별자에 대한 유효범위이다.
어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.
다만, 전역공간을 제외하면 오직 함수에 의해서만 스코프 생성된다.
이러한 '식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라 한다.
그리고 이를 가능케 하는 것이 바로 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference이다.
'선언하다'라는 행위가 실제로 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때 뿐이다. 어떤 함수를 선언(정의)하는 행위 자체도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문이다.
예를 들어,
A 함수 내부에 B 함수를 선언하고 다시 B 함수 내부에 C 함수를 선언한 경우, 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조한다.
함수 B의 LexicalEnvironment에 있는 outerEvironmentReference는 다시 함수 B가 선언되던 때(A)의 LexicalEnvironment를 참조한다.
이처럼 outerEnvironmentReference는 연결리스트 형태를 띈다.
'선언 시점의 LexicalEnvironment'를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment가 있다. 또한 각 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 있고 다른 순서로 접근하는 것은 불가능 하다.
이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다.
스코프 체인
var a = 1;
var outer = function () {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer();
console.log(a);
시작: 전역 컨텍스트가 활성화 된다. 전역 컨텍스트의 environmentRecord { a, outer } 식별자를 저장합니다. 전역 컨텍스트는 선언 시점이 없으므로 전역 컨텍스트의 outerEnvironmentReference에는 아무것도 담기지 않는다.
1번째 줄과 2번째 줄: 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당한다.
10번째 줄: outer 함수를 호출한다. 이에 따라 전역 컨텍스트의 코드는 10번째 줄에서 임시중단되고, outer 실행 컨텍스트가 활성화되어 2번째 줄로 이동한다.
2번째 줄: outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장한다. outerEnvironmentReference에는 outer 함수 선언될 당시의 LexicalEnvironment가 담긴다. outer 함수는 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조복사한다. 이를 [ GLOBAL, { a, outer } ] 라고 표기합니다. 첫 번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord 객체이다(this: 전역 객체).
3번째 줄: outer 스코프에 있는 변수 inner에 함수를 할당한다.
7번째 줄: inner 실행 함수를 호출합니다. 이에 따라 outer 실행 컨텍스트의 코드는 7번째 줄에서 임시중단되고, inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.
3번째 줄: inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장한다.
outerEnvironmentReference에는 inner 함수를 선언될 당시 LexicalEnvironment, 즉 { outer, { inner } }를 참조복사한다(this: 전역객체).
3번째 줄: outer 스코프에 있는 변수 inner에 함수를 할당한다.
7번째 줄: inner 함수를 호출한다. 이에 따라 outer 실행 컨텍스트의 코드는 7번째 줄에서 임시중단되고, inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.
3번째 줄: inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장한다.
outerEnvironmentRerence에는 inner 함수가 선언될 당시의 LexicalEnvironment가 담긴다. inner 함수는 outer 함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment, 즉 { outer, { inner } }를 참조복사한다.
4번째 줄: 식별자 a에 접근하고자 한다. 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색한다. a가 발견됐는데 여기에는 아직 할당이 된 값이 없다.
5번째 줄: inner 스코프에 있는 변수 a에 3을 할당한다.
6번째 줄: inner 함수 실행이 종료됩니다. inner 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 outer 실행 컨텍스트가 다시 활성화되면서, 앞서 중단했던 7번째 줄의 다음으로 이동한다.
8번째 줄: 식별자 a에 접근하고자한다. 이때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근한다. 첫 요소의 environmentRecord에서 a가 있는지 찾아보고, 없으면 outerEnvironmentReference에 있는 environmentRecord로 넘어가는 식으로 계속하는 검색한다. 예제에서는 두 번째, 즉 전역 LexicalEnvironment에 a가 있으니 그 a에 저장된 값 1을 반환한다(1 출력).
9번째 줄: outer 함수 실행이 종료된다. outer 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 전역 컨텍스트가 다시 활성화되면서, 앞서 중단했던 10번째 줄의 다음으로 이동
11번째 줄: 식별자 a에 접근하고자 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색한다. 바로 a를 찾을 수 있다! 이로써 모든 코드의 실행이 완료된다. 후에 전역 컨텍스트가 콜 스택에서 제거되고 종료한다.
전체 윤곽을 바라보면 '전역 컨텍스트 -> outer 컨텍스트 -> inner 컨텍스트'순으로 점차 규모가 작아지는 반면 스코프 체인을 타고 접근 가능한 변수의 수는 늘어난다.
그러나 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니다!
위 코드 상의 식별자 a는 전역 공간에서도 선언했고, inner 함수 내부에서도 선언했다. inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, 즉 inner 스코프 LexicalEnvironment에 a 식별자가 존재하므로 스코프 체인 검색을 더 진행하지 않고, 즉시 inner LexicalEnvironment 상의 a를 반환하게 된다.
즉, inner 함수 내부에서 a 변수를 선언했기 때문에 전역 공간에서 동일한 이름의 a 변수에는 접근할 수 없는 셈이다.
이를 변수 은닉화라고 한다.
위의 코드를 생각하며 전역변수와 지역변수의 의미를 알아보자
위 코드엣 전역변수는 전역 스코프에서 선언한 a와 outer 둘이다.
지역변수는 outer 함수 내부에서 선언한 inner와 inner 내부 함수 내부에서 선언한 a 둘이다. 즉 전역 공간에서 선언한 변수는 전역변수이고, 함수 내부에서 선언한 변수는 무조건 지역변수 이다.
실행 컨텍스트의 thisBinding에서 this로 지정된 객체가 저장된다.
실행 컨텍스트 활성화 당시 this가 지정되지 않은 경우에는 전역 객체가 저장된
다.
그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.