실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체다. 실행 컨텍스트가 활성화되는 시점에서 선언된 변수를 위로 올리고(호이스팅) 외부 환경 정보를 구성하고 this 값을 설정하는 등 동작을 수행한다.
동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓았다가 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다. "동일 환경"이란 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval(), 함수 등이 있다. 흔히 우리가 실행 컨텍스를 구성하는 방법은 함수를 실행하는 것이다!
스택(stack) 은 선입후출, 가장 처음 들어간 것이 가장 마지막에 나온다. 큐(queue)는 선입선출, 가장 처음에 들어가면 순차적으로 나온다
var a = 1;
function outer() {
function inner() {
console.log(a) // undefined
var a = 3
}
inner();
console.log(a) // 1
}
outer();
console.log(a) // 1
코드를 실행하면 전역 컨텍스트가 콜 스택에 담긴다. 그리고 outer 함수를 호출하면서 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해 outer 실행 컨텍스트를 생성하고 콜 스택에 담는다. 콜 스택의 맨 위에 outer 실행 컨텍스트가 놓인 상태가 됐으므로 전역 컨텍스트 관련 코드는 실행을 중단하고 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순차로 실행한다.
inner 함수 내부에서 a 변수에 값을 할당하면 inner 함수의 실행이 종료되면서 콜 스택에서 제거된다. 그렇게 전역 컨텍스트까지 쌓인 역순으로 실행된다.
실행 컨텍스트가 활성화될 때 자바스크립트 엔전은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트에 저장한다. 그 정보들은 다음과 같다
VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보, 선언 시점의 LexicalEnviroment의 스냅샷으로 변경 사항은 반영되지 않는다
LexicalEnviroment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간 반영된다.
ThisBinding: this 식별자가 가리키는 대상
실행 컨텍스트를 생설할 때 VariableEnvironment에 정보를 담은 다음 이를 그대로 복사해서 LexicalEnviroment를 만들고 이후에는 LexicalEnviroment을 주로 활용한다. VariableEnvironment와 LexicalEnviroment의 내부에는 enviornmentRecord와 outer-EnvironmentReference로 구성되어 있다.
LexicalEnviroment가 중요한 까닭은 변수나 함수의 값은 LexicalEnviroment이 어딘지, 즉 어디서 선언했는지에 따라 결정되기 때문이다.
enviornmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var 변수 식별자 등이 식별자에 해당된다. 컨텍스트 내부를 처음부터 끝까지 훑는다. var 변수와 함수 선언부를 최상단으로 끌어올리는 것(실제 그러진 않음 편의상)을 호이스팅이라고 한다.
function a(x) {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x)
}
a(1)
호이스팅 입장에선 인자란 다른 코드보다 먼저 선언 할당이 이루어진 것이다. 다르게 말하면
function a() {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x)
}
a()
이 상태에서 변수 정보를 수집하는 과정, 즉 호이스팅을 처리하게 된다. 변수명과 함수 선언부를 상단에 올린다. enviornmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있다. 각 식별자에 어떤 값이 할당되는지는 관심이 없다.
function a() {
var x;
var x;
var x;
x = 1;
console.log(x); // 1
console.log(x); // 1
x = 2;
console.log(x) // 2
}
a(1)
함수가 있는 호이스팅을 살펴보자.
function a(x) {
console.log(b)
var b = "bbb"
console.log(b)
function b() {}
console.log(b)
}
###
a()
호이스팅을 하게 되면
function a(x) {
var b;
var b = function b (){};
console.log(b); // [Function: b]
b = "bbb";
console.log(b); // bbb
console.log(b); // bbb
}
a()
function b가 호이스팅되면서 변수 b에 할당되었다! 변수는 선언부만 호이스팅이 되고 함수는 전체가 호이스팅 된다.
console.log('1--------'); // 1------
console.log(dongha); // undefined
console.log(say()); // hi
var dongha = 'dongha kim';
function say() {
console.log('hi');
}
변수 dongha와 함수 say 는 선언 전 콘솔로 호출하였다. 변수는 undefined를 출력하고 함수는 정상적으로 출력된다. 이는 자바스크립트가 메모리를 할당하는 방식의 차이 때문에 발생한다.
console.log(dongha); // ReferenceError
console.log(say()); // ReferenceError
const dongha = 'dongha kim';
function say() {
console.log('hi');
}
변수가 메모리에 저장될 때 3단계를 거친다.
var가 선언과 초기화가 동시에 진행되기 때문에 호이스팅 되면서 undefined가 되는데 let/const의 경우 선언과 초기화가 분리된다. 2단계의 경우 실제 코드가 진행되는 단계에서 진행된다 그래서 초기 값이 undefined가 아니라 ReferenceError로 출력된다.
참고 :
코어 자바스크립트
https://soldonii.tistory.com/62?category=862198
https://soldonii.tistory.com/57