02 실행 컨텍스트
2.1 실행 컨텍스트란?
실행 컨텍스트는 실행할 코드에 제공할 환경 정보를 모아놓은 객체이다. 하나의 실행 컨텍스트를 구성할 수 있는 방법으로는
- 전역공간
eval()
함수- 함수
우리가 흔히 실행 컨텍스트를 구성하는 방법을 함수를 실행하는 것 뿐이다.
// ---------------------- (1)
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3;
}
inner(); // ---------- (2)
console.log(a); // 1
}
outer(); // ------------ (3)
console.log(a); // 1
처음 코드를 실행하는 순간(1) 전역 컨텍스트가 콜 스택에 담긴다.
최상단의 공간은 별도의 실행 명령 없이도 브라우저에서 자동으로 실행하기 때문에 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 생각하면 된다.
(3)에서 outer
함수를 호출하면 엔진은 outer
에 대한 환경 정보를 수집해서 실행 컨텍스트를 생성한 후 콜 스택에 담는다.
(2)에서 inner
함수의 실행 컨텍스트가 콜 스택에 담긴다.
실행 컨텍스트 객체에 담기는 정보는
- VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 LexicalEnvironment의 스냅샵으로 변경사항은 반영되지 않음.
- LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨.
- ThisBinding:
this
식별자가 바라봐야 할 대상 객체
1.2 VariableEnvironment
실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담고, 이를 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용한다. 내부는 environmentRecord와 outer-EnvironmentReference로 구성되어 있다.
1.3 LexicalEnvironment
lexical environment는 "컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것"
2.3.1 environmentRecord와 호이스팅
environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있으면 그 함수 자체, var로 선언된 변수의 식별자 등 컨텍스트 내부 전체를 처음부터 끝까지 순서대로 수집한다.
전역 실행 컨텍스트는 변수 객체를 생성하는 대신 JS 런타임 환경이 제공하는 객체 (전역 객체)를 활용한다.
브라우저의 window
나 nodejs의 global
등이 있고, 이들은 JS 내장 객체가 아닌 호스트 객체로 분류된다.
코드가 실행되기 전에 이미 엔진은 해당 환경에 속한 코드의 변수명들을 알고 있게 된다. 엔진이 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행한다 라고 생각해도 해석하는데 문제될것이 없다. 이를 **호이스팅(hoisting)**이라고 하는데, 엔진이 실제로 끌어올리지는 않지만 편의상 끌어올린 것으로 간주한다는 것이다.
호이스팅 규칙
function a() {
var x = 1; // 수집 대상 1
console.log(x); // (1)
var x; // 수집 대상 2
console.log(x); // (2)
var x = 2; // 수집 대상 3
console.log(x); // (3)
}
a();
이 상태에서 변수 정보를 수집하는 과정, 호이스팅을 처리해보면, environmentRecord는 현재 실행될 컨텍스트의 어떤 식별자들이 있는지만 관심있고 어떤 값이 할당될 것인지는 관심이 없다. 따라서 변수명만 끌어올리고 할당 과정은 그대로 둔다.
function a() {
var x; // 수집 대상 1
var x;
var x;
x = 1;
console.log(x); // (1)
console.log(x); // (2)
x = 2; // 수집 대상 3
console.log(x); // (3)
}
a();
이로써 실제 콘솔의 출력은 1, 1, 2
가 된다.
function a() {
console.log(b);
var b = 'bbb';
console.log(b);
function b() {}
console.log(b);
}
a();
위 코드는
function a() {
var b;
var b = function b() {}
console.log(b);
b = 'bbb';
console.log(b);
console.log(b);
}
a();
이렇게 변하고 콘솔 출력에는 b함수, 'bbb', 'bbb'
가 나온다.
함수 선언문과 함수 표현식
- 함수 선언문 (function declaration) :
function
정의부만 존재하고 별도의 할당 명령이 없다.- 반드시 함수명이 정의돼 있어야 한다.
- 함수 표현식 (function expression) : 정의한
function
을 별도의 변수에 할당한다.- 함수명이 없어도 된다.
function a() {} // 함수 선언문
a(); // ok
var b = function () {} // (익명) 함수 표현식
b(); // ok
var c = function d() {} // 기명 함수 표현식
c(); // ok
d(); // error
console.log(sum(1, 2));
console.log(multiply(3,4));
function sum (a,b) {
return a + b;
}
var multiply = function (a,b) {
return a * b;
}
위 코드가 environmentRecord의 정보 수집 과정에서 호이스팅 과정을 마치면
var sum = function sum(a,b) { // 함수 선언문은 전체를 호이스팅 한다.
return a + b;
}
var multiply;
console.log(sum(1,2));
console.log(multiply(3,4));
multiply = function (a,b) { // 변수의 할당부는 원래 자리에 남겨둔다.
return a * b;
}
sum
함수는 선언 전에 호출해도 아무 문제 없이 실행된다. 이 부분이 어색할 수 있는데 호이스팅을 이해하더라고 거부감이 느껴질 수 있다.
'선언 후에 호출할 수 있다'라고 이해하는 편이 훨씬 자연스러우니까. 또 이 때문에 실무에서 버그를 만들 수 있다.
그래서 상대적으로 함수 표현식이 안전하다.
2.3.2 스코프, 스코프 체인, outerEnvironmentReference
스코프란 식별자에 대한 유효범위이다. ES5까지의 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다. 식별자의 유효범위를 안에서부터 바깥으로 차례대로 검색하는 것을 스코프 체인이라고 한다. 그리고 이게 가능한 이유는 outerEnvironmentReference 때문이다.
스코프 체인 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);
- 전역 컨텍스트의 LexicalEnvironment는
a, outer
- outer 컨텍스트의 LexicalEnvironment는
inner
, outerEnvironmentReference는전역 LE
- inner 컨텍스트의 LE는
a
, outerEnvironmentReference는outer LE
inner
의console.log(a)
는undefined
- inner의 LE에서 a가 있기 때문에 검색을 종료하고, 아직 값이 할당되기 전이기 때문에
undefined
를 출력한다.
outer
의console.log(a)
는 1
- outer의 LE에는 a가 없기 때문에, outerEnvironmentReference에서 a를 찾고 그 값인 1이 출력됨
- 전역 컨텍스트에서
console.log(a)
는 1
- 전역 LE에서 a를 찾았고 그 값인 1을 출력
전역변수와 지역변수
위 예제에서 전역변수는 a
와 outer
이다. 지역변수는 outer
내부에서 선언한 inner
와 inner
내부에서 선언한 a
이다.
2.4 this
실행 컨텍스트의 thisBinding
에는 this
로 지정된 객체가 저장된다. 실행 컨텍스트 활성화 당시 this
가 지정되지 않으면 this
는 전역 객체가 저장된다.