이 사이트를 번역하여 정리했습니다.
Eval 함수 내부 코드를 위한 컨텍스트로 일반적으로 사용하지 않는다.
calling stack이라고 알려져 있는데 스택 구조로 이루어져 있다. JS에선 실행 컨텍스트는 스택 구조를 이용해서 관리된다.
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
- 전역 코드가 실행되면 전역 실행 컨텍스트가 생성되고 스택에 쌓인다.
- 함수를 호출하면 해당 함수의 컨텍스트가 생성되고 스택에 쌓인다.
- 함수가 종료되면 함수 실행 컨텍스트를 파기하고 이전 컨텍스트로 실행 컨트롤을 반환한다.
실행 컨텍스트는 생성과 실행의 두 단계를 거친다.
생성 단계에서는 다음의 두 컴포턴트를 형성한다.
ExecutionContext = {
LexicalEnvironment = <ref. to LexicalEnvironment in memory>,
VariableEnvironment = <ref. to VariableEnvironment in memory>,
}
렉시컬 환경이란 ECMAScript 코드
의 lexical nesting structure
에 기반한 특정한 변수와 함수의 연결을 정의하는 구조다.
쉽게 말해서 식별자와 변수의 맵핑을 담당한다고 할 수 있다.
var a = 20;
var b = 40;
function foo() {
console.log('bar');
}
위의 코드를 예시로 렉시컬 환경을 아래와 같이 표현할 수 있다.
lexicalEnvironment = {
a: 20,
b: 40,
foo: <ref. to foo function>
}
렉시컬 환경은 다음 세 가지 컴포넌트를 가진다
Environment Record
렉시컬 환경에서 변수와 함수의 선언을 저장하는 공간이다. ER은 두 가지 타입이 있다.
참고로, ER은 arguments
라는 객체를 포함하는데 아래와 같다.
function foo(a, b) {
var c = a + b;
}
foo(2, 3);
// argument object
Arguments: {0: 2, 1: 3, length: 2},
Outer Environment Reference
ES3에서 공식적으로 사용되던 용어는 Scope Chain이며, ES5부터는 용어가 바뀌었다.
함수가 중첩 상태일 때 하위함수부터 상위함수로 탐색을 한다. 만약에 전역 실행 컨텍스트에서도 탐색에 실패하면 참조 오류가 발생한다.
This Binding
이전 포스팅에서 다루었다
ES6에서는 렉시컬 환경과 변수환경의 차이점은 하나이다. 렉시컬 환경은 함수와 let
/const
변수 바인딩에 관한 정보를 저장하는 반면, 변수 환경은 var
변수 바인딩에 관련된 정보만 저장한다.
컨텍스트 생성 단계에서 모든 변수에 대한 할당이 끝나면 실행 단계로 넘어간다.
아래의 예제를 따라가자.
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
위의 코드가 실행되면, JS 엔진은 전역 코드를 실행하기 위한 전역 실행 컨텍스트를 생성한다. 생성된 컨텍스트는 아래와 같다.
GlobalExectionContext = {
// 렉시컬 환경
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 전역 변수인 a, b 저장
// 전역 함수인 multiply 저장
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
// 변수 환경
EnvironmentRecord: {
Type: "Object",
// var 형 변수인 c는 변수 환경에 저장된다.
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
// let과 const로 선언된 변수는 초기화되지 않은 상태로 할당되고,
//var로 선언된 변수는 기본값(undefined)가 할당되어 있다.
//이는 호이스팅과 관련이 되어 있다.
//이러한 동작 이유로 var은 선언되기 전에 참조할 수 있지만,
//let과 const는 불가능하다(참조 에러)
실행 단계로 넘어가면, 모든 변수에 대한 할당이 완료되어 있다.
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 전역 변수 값이 할당된다.
a: 20,
b: 30,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
이제 multiply
함수를 마주치게 되면, 새로운 함수 실행 컨텍스트를 생성한 뒤에, 스택에 쌓는다.
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// argument 객체에 저장된다.
Arguments: {0: 20, 1: 30, length: 2},
},
//상위 컨텍스트를 가리키는 outer
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: undefined
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
이후에 실행 단계로 넘어간다.(함수 실행 컨텍스트의 변수 할당이 모두 완료됨)
여기서 잠깐 헷갈릴 수 있는 부분은 위에서 이미 실행단계로 넘어갔다고 생각할 수 있다는 것이다. 모든 실행 컨텍스트는 똑같이 생성과 실행 단계가 있다. 따라서 앞서 전역 실행 컨텍스트도 실행단계가 있고, 현재 함수 실행 컨텍스트에서도 실행단계가 있다.
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
g: 20
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
함수 실행이 완료되면, return
값이 c에 저장된다. 그리고 전역 렉시컬 환경에 업데이트 하게 된다. 전역 코드가 완료되면 프로그램이 종료된다.