자바스크립트는 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 상단으로 끌어 올리고 (호이스팅), 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행하는데 이 과정에서 다른 언어에선 볼 수 없는 현상들이 발생합니다.
실행 컨텍스트 는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 동적 언어로서의 자바스크립트 성격에 대해 가장 잘 파악할 수 있는 개념입니다.
자바스크립트는 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 넣었다가 가장 위에 있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다.
하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval(), 함수 등이 있는데 개발자가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 방법 뿐입니다.
예제 코드
var a = 1;
function outer() {
function inner() {
console.info(a); // undefined
var a = 3;
}
inner();
console.info(a); // 1
}
outer();
console.info(a); // 1
상단의 코드를 참고하여 콜 스택에 실행 컨텍스트가 어떤 순서로 쌓이고, 어떤 순서로 코드 실행에 관여하는지는 하단의 그림을 통해 자세히 알아보겠습니다.
이렇게 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장합니다. 이 객체는 자바스크립트 엔진이 활용할 목적으로 생성되므로 개발자가 코드를 통한 확인은 불가능합니다. 이 객체는 아래와 같은 정보들을 담고 있습니다.
environmentRecord 에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다.
식별자에는 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var 로 선언된 변수의 식별자 등이 해당됩니다.
컨텍스트 내부 전체를 처음부터 끝까지 훑으며 순서대로 수집합니다.
변수 정보를 수집하는 과정을 모두 마쳤어도 아직 실행 컨텍스트가 관여하는 코드들을 실행되기 전 상태이므로, 이는 즉 자바스크립트 엔진이 코드 실행 전 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있다는 뜻입니다.
위의 말은, 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려 놓은 다음 실제 코드를 실행한다 라고 자바스크립트 엔진 동작 방식을 받아들여도 무방합니다. 이 개념이 호이스팅 입니다.
실제로 끌어 올리는건 아니지만, 자바스크립트 엔진이 끌어 올린 것으로 간주하는 것입니다.
OuterEnvironmentReference를 이해하기 위해서는 스코프(scope) 개념을 먼저 이해해야 한다.
스코프란 식별자에 대한 유효 범위를 말한다.
자바스크립트에는 전역 스코프(global scope)와 지역 스코프(local scope)가 있다.
var a = 1;
function scope() { // 함수 스코프
var b = 2;
console.log(a); // 1
console.log(b); // 2
}
console.log(a); // 1
console.log(b); // b is not defined
scope 함수 외부에서 선언한 변수 a는 함수 scope 안에서도 접근이 가능하다. 그러나 함수 안에서 선언한 변수 b는 오직 함수 안에서만 접근할 수 있다. 변수 a는 전역 스코프에, 변수 b는 지역 스코프에 있기 때문이다. 자바스크립트는 변수의 유효 범위를 검색할 때 안에서부터 바깥으로 찾아나가는데, 이것을 스코프 체인(scope chain)이라고 한다.
따라서 전역 스코프에 선언된 변수들은 어느 곳에서도 접근이 가능하다. 반면 지역 스코프는 선언된 함수의 내부에서만 접근이 가능하다.
OuterEnvironmentReference는 현재 호출된 함수가 선언되는 시점에서의 Lexical Environment를 참조하는 포인터이다. OuterEnvironmentReference는 연결리스트 형태를 띄며 ‘선언 시점의 LexicalEnvironment’를 계속 찾아 올라간다. '선언 시점의 LexicalEnvironment'라는건 결국 해당 함수가 속한 상위 스코프의 범위이다.
각 함수의 OuterEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있기 때문에 가장 가까운 요소부터 위로 차례대로만 접근할 수 있다.
var outerWrap = function() {
var a = 1;
var outer = function() {
var b = 2;
var inner = function() {
console.log(a, b); // inner의 LexicalEnvironment에는 a와 b가 없지만 스코프 체인을 통해 값에 접근한다.
console.dir(inner);
}
inner();
console.log(a); // outer의 LexicalEnvironment에는 a가 없지만 스코프 체인을 통해 값에 접근한다.
}
outer();
}
outerWrap();
중첩 구조가 많지만 각 함수들이 자신이 선언된 시점의 상위 LexicalEnvironment를 참조하고 있다는 것만 알면 어렵지 않다. 위의 코드에서 console.dir(inner)로 inner 함수의 계층 정보를 열어보면 [[scopes]] 프로퍼티에서 상위 스코프 정보를 확인할 수 있다.
0번째 스코프에는 함수 outer의 LexicalEnvironment에서 선언된 변수 b와 함수 inner가 있다. 한 단계 올라간 1번째 스코프에는 함수 outerWrap의 LexicalEnvironment에서 선언된 변수 a가 있다. 그 다음 단계로 올라가면 전역 컨텍스트가 노출된다. 이렇게 체인을 통해서 상위 스코프로 접근할 수 있음을 알 수 있다.
참고로, 전역 컨텍스트에서의 OuterEnvironmentReference는 null이다.
출처: https://velog.io/@ggong/자바스크립트의-실행-컨텍스트-execution-context
https://grepper.tistory.com/m/84