실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로 호이스팅, this 바인딩 등의 정보가 담긴다. 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보(Lexical Environmen)를 구성하고, this 값을 설정하는 등의 동작을 수행한다.
흔히 실행 컨텍스트를 생성하는 방법은 함수를 실행하는 방법이다. (= 함수가 실행될 때 실행 컨텍스트가 만들어진다.)
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3;
}
inner();
console.log(a); // 1
}
outer();
console.log(a); // 1
이미지 출처 : 코어 자바스크립트(정재남)
*한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점이다. 실행 컨텍스트에 담기는 객체는 개발자가 코드를 통해 확인 할 수 없으며 다음과 같은 정보들이 담긴다.
호이스팅
스코프(scope)는 사전적 의미로 범위
라는 뜻을 가지고 있다. 의미 그대로 식별자에 대한 유효범위를 뜻한다. 자바스크립트는 전역 스코프(global scope)와 지역 스코프(local scope) 2가지 타입을 가지고 있다.
/* 예제 1 */
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함수의 외부뿐 아니라 함수 내부에서도 접근이 가능하다. scope함수의 내부에서 선언한 변수 b는 오직 scope함수의 내부에서만 접근할 수 있다. 변수 a를 전역 스코프(global scope), b를 지역 스코프(local scope)라고 한다.
위의 예시처럼 전역 스코프는 어느곳에서 호출해도 해당 변수에 접근이 가능하지만, 지역 스코프는 선언된 함수의 내부에서만 접근이 가능하다.
/* 예제 2 */
var a = 1;
var truth = true;
if (truth) {
var b = 2;
console.log(a); // 1
console.log(b); // 2
}
console.log(a); // 1
console.log(b); // 2 ← ???
그런데 예제1과 다르게 예제 2에서는 if문 안에서 정의한 변수b가 외부에서 접근이 가능하다. 위의 예제와의 차이점은 뭘까?
그건 예제 1은 함수 스코프 (function scope)이고 예제 2는 블록 스코프 (block scope)라는 점이다. ES5까지의 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서 스코프가 생성된다. 블록 스코프인 if문에서는 변수를 선언하면 전역변수가 되기 때문에 예제 2에서의 변수 b는 전역 스코프라고 할 수 있다. 즉 스코프는 함수가 정의될 때 결정된다.
*ES6부터는 블록에 의한 스코프가 가능해졌다. 블록스코프 내부에서 var가 아닌 let, const변수를 사용하거나 strict 모드를 이용하면 블록 스코프 사용이 가능하다.
이러한 '변수의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인(scope chain)
이라고 한다.
스코프가 [[scope]] 프로퍼티로 각 함수 내에서 연결리스트 형식으로 관리되는데 이 스코프간의 상하관계를 스코프 체인이라고 한다. LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference는 현재 호출된 함수가 선언될 당시(과거)의 LexicalEnvironment를 참조한다.
var a = 1;
function one() {
var b = 2;
function two() {
var c = 3;
function three() {
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
}
three();
}
two();
}
one();
one함수 내부에 two함수를 선언하고 또 그 내부에 three함수를 선언한 경우,
이처럼 outerEnvironmentReference는 연결리스트 형태를 띄며 '선언 시점의 LexicalEnvironment'를 계속 찾아 올라간다. 세 함수의 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있기 때문에 가장 가까운 요소부터 위로 차례대로만 접근할 수 있다.
이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.
var a = 1;
function outer() {
var b = 2;
function inner() {
console.log(b); // 2
console.dir(inner);
}
inner();
}
outer();
크롬 브라우저에서 스코프 체인 중 현재 실행 컨텍스트를 제외한 상위 스코프 정보들을 개발자 도구 console을 통해 간단하게 확인할 수 있다.
위의 예제에서 console.dir(inner)로 출력한 결과물은 다음과 같다.
inner함수의 상위 스코프 정보들을 확인할 수 있다. inner함수에는 outer함수의 LexicalEnvironment가 담겨있는데 outer함수에는 LexicalEnvironment가 담겨있다. 때문에 체인을 통해 inner함수에서 전역 컨텍스트까지 접근할 수 있는것을 확인 할 수 있다.
참고 : 코어 자바스크립트(정재남), MDN