자바스크립트 엔진은 소스코드를 '평가'와 '실행' 두가지 과정으로 나누어 처리합니다.
식별자(변수, 함수, 클래스 등의 이름)를 등록하고 관리하는 스코프(by 렉시컬 환경)와 실행 순서 관리(by 실행 컨텍스트 스택)를 구현한 내부 메커니즘이다.
const x = 1;
function foo() {
const y = 2;
function bar() {
const z = 3;
console.log(x + y + z);
}
bar();
}
foo();
x
와 foo
는 전역 실행 컨텍스트에 등록이 됩니다. x
에 값이 할당이 되며 전역함수 foo
가 호출이 됩니다.foo
함수가 호출되면 전역 코드의 실행이 멈추게 됩니다. foo
함수 내부의 함수 코드를 평가하여 foo
함수 실행 컨텍스트를 생성한 후 컨텍스트 스택에 푸쉬합니다. 이때, foo
함수의 지역변수 y
와 중첩함수 bar
가 실행 컨텍스트에 등록됩니다. y
에 값이 할당되며, bar
가 호출이 됩니다.bar
함수가 호출이 되면 foo
함수 실행이 멈추게 됩니다. bar
함수 내부의 함수 코드를 평가해 bar
함수 실행 컨텍스트를 생성한 후 컨텍스트 스택에 푸쉬합니다. 이 때 지역변수 z
가 컨텍스트에 등록됩니다. z
에 값이 할당되며 console.log
메서드를 호출한 이후 bar
함수는 종료됩니다.❗
console.log
도 함수 이기 때문에 실행 컨텍스트를 만드나 그림에서는 생략되었습니다.
bar
함수가 종료되면 bar
함수 실행 컨텍스트를 컨텍스트 스택에서 제거(pop)합니다.foo
함수도 더이상 실행 할 코드가 없기 때문에 종료됩니다.foo
함수를 실행 컨텍스트 스택에서 제거(pop)합니다.식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트 입니다.
Lexical Environment은 두 개의 구성요소로 이루어져 있습니다.
실행 컨텍스트 스택에서 설명한 코드 실행과정을 Lexical Environment를 포함해서 더욱 자세하게 설명해 보도록 하겠습니다.
var x = 1;
const y = 2;
function foo(a) {
var x = 3;
const y = 4;
function bar(b) {
const z = 5;
console.log(a + b + x + y + z);
}
bar(10);
}
foo(20);
전역 객체 생성
전역객체가 전역 코드 평가 이전에 생성이 됩니다.
전역 코드 평가
전역 실행 컨텍스트 생성
전역 실행 컨텍스트를 생성 후 컨텍스트 스택에 푸쉬합니다.
Lexical Environment 생성
전역 Lexical Enviroment를 생성 후 실행 컨텍스트에 바인딩 합니다.
2.1 객체 환경 레코드 생성전역 환경 레코드를 구성하고 있는 객체 환경 레코드는
binding object
를 참조하게 됩니다. binding object
는 처음에 생성했던 전역 객체 입니다.
window
키워드 없이 전역객체에 접근할 수 있습니다.binding object
를 통해 전역 객체에 변수 식별자를 키로 등록한다음 undefined를 할당합니다. 따라서 실행전에 변수를 식별할 수 있습니다. 이는 변수 호이스팅이 발생하는 원인이 됩니다. 2.2 선언적 환경 레코드 생성
let, const 키워드로 선언한 전역변수는 선언적 환경 레코드에 등록되고 관리됩니다.
this 바인딩
전역 환경 레코드의 내부 슬롯에 this가 바인딩 됩니다. 일반적으로 전역 코드에서 this는 전역 객체를 가리키므로 전역 환경 레코드의 내부 슬롯에는 전역 객체가 바인딩 됩니다.
외부 렉시컬 환경에 대한 참조 결정
상위 스코프를 참조하는 단계입니다. 전역 코드의 경우 상위 스코프는 존재하지 않기때문에 null로 초기화 됩니다.
전역 코드 실행
선언문을 제외한 전역 코드가 실행이 됩니다. 변수 할당문이나 함수 호출문을 실행하기 전, 실행 컨텍스트의 Lexical Environment의 Environment Record를 통해 선언된 식별자인지 확인해야 합니다. 그 후 값을 할당하게 됩니다.
스코프 체인이란?
현재 실행 중인 컨텍스트에서 식별자를 검색하게 되는데 만약 존재 하지 않을경우 Lexical Environment의 Outer Lexical Environment Reference를 통해 상위 스코프에서 식별자를 검색하게 됩니다.
foo 함수 코드 평가
함수 실행 컨텍스트 생성
foo 함수 실행 컨텍스트를 생성합니다. 함수 실행 컨텍스트는 Lexical Environment가 완성된 다음 컨텍스트 스택에 푸쉬 됩니다.
함수 Lexical Environment 생성
2.1 함수 Environment Record 생성
함수 Lexical Environment를 구성하는 컴포넌트 중 하나인 함수 Environment Record는 매개변수, arguments 객체, 함수 내부에서 선언한 지역변수와 중첩 함수를 등록합니다.
2.2 this 바인딩
foo
함수가 일반함수로 호출되었으므로 this는 전역객체를 가리키게 됩니다.
2.3 외부 렉시컬 환경에 대한 참조 결정
외부 렉시컬 환경에 대한 참조에
foo
함수 정의가 평가된 시점에 실행 중이던 컨텍스트의 렉시컬 환경이 할당 됩니다. foo
함수가 평가된 시점에 실행 중이던 컨텍스트는 전역 컨텍스트 이므로 전역 컨텍스트의 렉시컬 환경을 참조하게 됩니다.
foo 함수 코드 실행
매개변수에 값이 할당되며, 변수 할당문이 실행되어 지역변수에 값이 할당 됩니다. 또한 bar
함수가 실행 됩니다.
bar 함수 코드 평가
foo
함수 평가 단계와 동일합니다.
bar 함수 코드 실행
bar 함수 코드 실행 종료
bar
실행 컨텍스트가 종료되고 컨텍스트 스택에서 제거됩니다.
실행 컨텍스트가 제거 되었다고 해서 bar
함수의 Lexical Environment까지 제거되는 것은 아닙니다. 만약 누군가가 참조하고 있다면, bar
함수의 Lexical Environment는 사라지지 않습니다.
foo 함수 코드 실행 종료
전역 코드 실행 종료
let
, const
는 var
와 달리 블록 레벨 스코프를 따릅니다. 블록 레벨 스코프를 실행 시킬경우 컨텍스트가 어떻게 변하는지 살펴보겠습니다.
let x = 1;
if (true) {
let x = 10;
console.log(x);
}
console.log(x);
코드의 블록이 실행되면 선언적 환경을 갖는 Lexical Environment를 생성 후 기존의 Lexical Environment를 대체하게 됩니다.
아래의 그림과 같습니다.
코드 블록의 실행이 종료되면 다시 이전의 Lexical Environment로 돌립니다.