Execution context
는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념으로 쉽게 생각하자면, 코드가 실행되기 위해 필요한 환경으로 이해하면 된다.
해당 환경에는 다음과 같은 정보가 필요
하며 이러한 정보들을 형상화하고 구분하기 위해 객체의 형태
로 생성하고 관리한다.
실행 가능한 코드
모든 실행 가능한 코드
는 다음 과정을 거친다.
코드의 평가 과정
해당 과정에서 실행 컨텍스트가 생성된다. 변수, 함수, 클래스 등의 선언문이 우선 평가되고 그 결과가 실행 컨텍스트에 등록된다.
코드의 실행
선언문을 제외한 코드를 순차적으로 실행한다. 이 때의 과정이 곧 런타임을 의미한다.
전역 실행 컨텍스트(Global Execution Context)
함수 실행 컨텍스트(Function Execution context)
Eval Function Execution Context
(잘 쓰이지 않음)우선, Global Execution Context 와 Function Execution Context를 추적 할 순서를 기억하기 위해서
자바스크립트 엔진은 call stack
이라는 Data structure를 사용한다. ( LIFO )
📝 예시코드
let a = 'Hello World!';
function first() {
second();
}
function second() {
let b = 'second';
}
first();
자바스크립트 엔진이 처음 코드를 실행하게 되면 전역 실행 컨텍스트(Global Execution Context)가 생성되고 call stack에 push
된다. (전역 실행 컨텍스트는 페이지가 종료될 때까지 유효)
first함수를 호출 할 때, first의 함수 실행 컨텍스트(Function Execution context)를 생성하고 현재 stack에 push
한다.
first함수 내에서 second함수를 호출 할 때, second의 함수 실행 컨텍스트(Function Execution context)를 생성하고 현재 stack에 push
한다.
second함수의 실행이 완료되면, stack에서 second의 함수 실행 컨텍스트(Function Execution context)를 pop
한다.
first함수의 실행이 완료되면, stack에서 first의 함수 실행 컨텍스트(Function Execution context)를 pop
한다.
모든 코드가 실행되고나면, 자바스크립트 엔진은 stack에서 전역 실행 컨텍스트(Global Execution Context)를 제거
한다.
1)Lexical Environment
와 2)Variable Environment
로 나뉜다.Lexical Environment
는 블록의 유효범위 안에 있는 변수와 함수 식별자를 매핑하는데 사용된다. (식별자(identifier)
란 참조 대상 식별자로써 함수와 변수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름이다.)
함수와 변수를 {name: value}
형태로 저장한다.
Lexical Environment
는 세가지 일을 한다. (VariableEnvironmnet도 동일)
Environment Record
는 Lexical Environment에 변수와 함수 식별자의 바인딩을 기록하는 객체이다.
function의 경우에는 arguments가 포함된다.
Environment Record는 또 다시 1.1.1)Declarative environment record
와 1.1.2)Object environment record
으로 나뉜다.
변수 선언 및 함수 선언, Try문의 Catch 절
에서 사용되는 식별자들의 바인딩을 관리한다.글로벌 함수와 변수에 사용.
with문
과 같이 식별자를 특정 객체 속성으로 취급할 때 사용되며, 이를 위해 bindingObject라는 프로퍼티에 바인딩한다.
식별자(identifier) 검색을 위해 외부 Lexical Environment를 참조하는 포인터로 Lexical nesting structure(scope Chain)에서 스코프 탐색을 위해 사용
Global Lexical Environment에서는 null
Function Lexical Environment에서는 상위 실행환경의 Lexical Environment를 참조
Function Lexical Environment에서 상위 lexical environment를 참조한다는 의미는 다음과 같다.
function a(){ var myVar = 2; b(); } function b(){ console.log(myVar); } var myVar = 1; a(); // ?
위의 코드를 보면 2가 찍힐 줄 알았는데 1이 나오게된다.
이유는 호출 시점에 실행 컨텍스트가 생성되는 것은 맞지만 Outer Environment Reference를 정의할 때호출 시점의 상위 lexical environment가 아니라 선언된 시점의 상위 lexical environment를 참조
한다고 생각하면 쉽다. 이는렉시컬 스코프(lexical scope)
라고도 한다.
https://jsbeginners.com/understanding-the-weird-parts-notes-1/추가적인 예시코드로 아래를 실행해보면 이해하는데 도움이 될 것이다.
function one(){ var a = 5; var b = 10; two(); } function two(){ console.log(a); console.log(b); } var a = 1; one();
'this'
값 결정 (브라우저의 경우 글로벌에서는 Window를 가리키며 함수에서의 this 값은 어떻게 함수가 호출되었는지에 따라 달라진다.) 📌 this관련하여 추후작성Lexical Environment와 동일하게 식별자들의 바인딩에 대한 정보
를 가지고 있다.
초기값을 복원
할 때 사용하기 위해 만들어졌다. (Lexical Environment의 값은 실행 중에 변경될 수도 있다.)
LexicalEnvironment에서는 let과 const
의 바인딩을 저장하며 VariableEnvironment에서는 var
에 대해서 저장한다.
위에서 실행 컨텍스트의 구조를 알아보았고 예시코드를 통해 실제로 어떻게 실행 컨텍스트가 생성되는지 알아보자.
📝 예시 코드
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
전역 코드에 진입하면 전역 코드를 한 줄씩 실행하기 이전에 전역 실행 컨텍스트가 아래와 같은 형태로 생성이 된다.
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
LexicalEnvironment에 함수와 let, const 변수를 매핑
했지만 let, const로 선언된 변수의 초기값은 할당되지 않았다.(이는 아직 메모리 공간 확보가 안되었다는 의미)
VariableEnvironment에 var로 선언된 변수
는 메모리에 매핑되며 초기값으로 undefined 할당
글로벌에서 Outer Environment Reference
는 null을 가리킨다.
글로벌에서 ThisBinding
는 Global Object 즉, Window이다.
전역 코드 평가가 끝나고나면,
전역 코드를 순차적으로 실행한다.
이 때, 위에서 선언되었던 변수들에 실제 값을 할당한다.
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: 20,
b: 30,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}
let, const로 선언된 변수의 초기값
은 여기서 할당된다.multiply(20, 30) 함수가 호출되는 시점에 전역 코드의 실행이 멈추고 호출된 함수 내부로 진입하며
함수 실행 컨텍스트를 아래와 같은 형태로 생성
한다.
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined(strict mode)>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: undefined
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined(strict mode)>
}
}
함수 실행 컨텍스트의 Environment Record에는 arguments가 포함
되어 있다.
Outer Environment Reference
는 상위 scope인 GlobalLexicalEnvironment를 가리킨다.
ThisBinding
는 어떻게 함수가 호출되었는지에 따라 달라지는데 여기서는 여전히 Window이다.
multiply(20, 30) 함수 코드 평가가 끝나고나면, 마찬가지로
함수 코드를 순차적으로 실행하며 변수들에 실제 값을 할당한다.
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: 20
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}
multiply(20, 30) 함수 코드가 순차적으로 실행되고 종료되면 call stack에서 pop되며
전역 실행 컨텍스트의 C 값이 업데이트
된다. 그 후 다음의 전역 코드를 실행한다.
그 후 전역 코드가 종료되면 call stack에서 전역 실행 컨텍스트를 pop하며 프로그램이 종료된다.
var a = 5;
function test(){
console.log(a);
console.log(b);
}
test();
var b = 5;
let a = 5;
function test(){
console.log(a);
console.log(b);
}
test();
let b = 5;
var는 평가 과정에서 이미 undefined로 초기화되었지만 let은 평가 과정에 매핑만 할뿐 초기화는 해주지 않았다. 그리고 함수가 실행되는 시점에서 두 코드다 변수 a는 실행 과정까지 거쳐 실제 값이 할당 되었지만 변수 b는 실제 값이 할당되지 않은 상태이기 때문에 let b에서 ReferenceError가 발생하는 것이다.
/ 추가적으로 코드 블럭일때도 실행 컨텍스트가 생성되는지 궁금하다... /
function callFunc() {
let a = 5;
{ // 이때도 실행 컨텍스트가 생기는가?
let a = 100;
console.log(a);
}
console.log(a);
}
callFunc();