자바스크립트 코드는 크게 (1)생성 단계와 (2)실행 단계로 나뉘어 작동된다.
- 생성 단계(Creation Phase): 전체 코드를 스캔하면서 실행 컨텍스트(Execution Context)를 생성하고 선언문(변수, 함수 선언)만 실행해서 환경 레코드(Environment Record)에 기록(=record).
- 실행 단계(Execution Phase): 나머지 코드를 순차적으로 실행. 환경 레코드에 생성한 정보들을 참조하거나 업데이트.
실행 컨텍스트(Execution Context)란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체다. 작성된 코드를 처음부터 끝까지 훑어보면서 필요한 정보들을 먼저 기록해두는 것이다.
이를 실생활에 비유하자면 서점에서 처음으로 보는 책을 훑어볼 때 목차부터 살펴보면서 대략 어떤 내용들을 담고 있는지 키워드들을 머리에 담는 것과 비슷한 과정이라고 이해했다.
자바스크립트는 실행 컨텍스트가 구성되는 시점에 크게 3가지를 생성한다.
- VariableEnvironment
- LexicalEnvironment
- ThisBinding
VariableEnvironment와 LexicalEnvironmet 모두 식별자 정보를 수집한다.
다만 값의 변화가 실시간으로 반영되느냐의 여부에 차이가 있다.
VE는 최초 실행 시의 스냅샷만 유지하는 용도다. 반면 LE는 값의 변화를 반영하기 때문에 자바스크립트 동작 원리를 이해하려면 LexicalEnvironment를 이해해야 한다.
LexicalEnvironment를 그대로 해석하면 '어휘적 환경'이다.
코어 자바스크립트의 저자 정재남 님은 이 개념을 '사전'과 비슷하다고 설명한다.
즉, 실행 컨텍스트를 구성하는 환경 정보들을 모아 사전처럼 구성한 객체라는 것이다.
사실 배우는 입장에서는 그동안 '어휘적 환경'이라는 표현이 모호하게 느껴졌는데, 이를 '사전적'이라는 표현으로 대체하니 이해가 훨씬 쉬웠다. 즉, 단순히 어떤 용어, 단어를 뜻하는 게 아니라 '실행되는 환경 속에 있는 모든 어휘(식별자, 외부 정보 등)들을 담은 사전'과 같은 역할을 하는 것이 LexicalEnvironment인 것이다.

🗨️ TMI: 'Lexicon'은 '사전'을 뜻한다. 과거 옥스포드 대학 출판부에서 운영하는 Lexico라는 사전 사이트도 있었다.
LexicalEnvironment는 다음 정보들을 담고 있다.
- environmentRecord
- outerEnvironmentReference
environmentRecord(환경 기록)는 현재 컨텍스트에 선언된 식별자 정보 수집(기록)한다. 이 과정에서 호이스팅(Hoisting)이 발생한다.
outerEnvironmentReference(외부 환경 참조)는 현재 컨텍스트의 외부에 있는 LexicalEnvironment를 참조한다. 이 과정에서 스코프 체인(Scope Chain)이 발생한다.
그럼 왜 호이스팅과 스코프 체인이 발생하는지 살펴보자.
호이스팅(Hoisting)은 자바스크립트에서 선언문이 최상단으로 끌어올려진 듯한 현상을 의미한다. 물리적으로 끌어올려지는 것은 아니지만 실행 컨텍스트가 생성되면서 코드 실행 전에 environmentRecord에 선언문들이 수집되기 때문에 일어나는 현상이다.
호이스팅은 선언한 변수와 함수에서 발생한다.
변수 호이스팅의 경우 어떤 키워드로 선언되었는지에 따라 다른 결과가 반환된다.
- var
- const, let
var는 선언과 초기화가 동시에 진행된다. 선언은 메모리 공간을 확보하고 식별자와 연결하는 과정이고, 초기화는 식별자에 암묵적으로 undefined 값이 binding 되는 것을 의미한다.
반면 ES6 문법에서 등장한 let, const는 호이스팅 과정에서 선언만 하고 초기화를 하지 않는다.
따라서 다음과 같은 코드가 있을 때 어떤 키워드로 선언했는지에 따라 실행 결과가 달라진다.
console.log(TVChannel) // undefined
var TVChannel = Netflix;
console.log(TVChannel) // Netflix
생성 단계: TVChannel이라는 변수를 선언하고 초기화(undefined)한다.
실행 단계
console.log(TVChannel) // Reference Error
const TVChannel = Netflix;
console.log(TVChannel) // Netflix
var와 달리 const로 선언할 경우, 첫 번째 줄에서 Reference Error가 발생한다.
const는 값을 선언할 때 undefined로 초기화하지 않기 때문이다.
let과 const로 변수를 선언할 때 선언 이전에 식별자를 참조할 수 없는 현상을 일시적 사각지대(Temproal Dead Zone)이라고 한다.
함수 호이스팅의 경우에도 함수의 형태에 따라 다르게 동작한다.
var, let, const로 변수를 선언할 때처럼 함수를 Function 키워드로 선언하는 형태의 함수
function name([param[, param,[..., param]]]) {
[statements]
}
함수를 별도의 변수에 할당하는 것으로 함수명을 정의하지 않아도 된다.
var myFunction = function [name]([param1[, param2[, ..., paramN]]]) {
statements
};
함수 선언문은 evrionmentRecord에 온전히 기록되어 함수 선언 이전에 호출해도 정상적으로 함수가 실행되지만,
함수 표현식은 var, let, const에 따라 결과가 달라진다.
1) 함수 표현식을 var로 선언하는 경우
var myFunction = () => { }
var로 선언하는 경우 undefined로 선언되어 이전에 실행하려고 하면 Type Error가 발생한다.
2) 함수 표현식을 let, const로 선언하는 경우
let, const로 선언한 경우 undefined로 초기화가 되어 있지 않으므로 Referrence Error가 발생한다.
그럼 이번에는 outerEnvironmentReference(외부 환경 참조)를 통해 발생하는 스코프 체인을 알아보자.
실행 컨텍스트에서 LexicalEnvironment는 먼저 environmentRecord에서 변수를 찾는다. 그런데 만약 내부 레코드에 변수의 값이 없는 경우 어떻게 할까?
outerEnvironmentReference을 통해서 상위의 스코프(범위)에서 값을 찾게 된다. 이때 하위의 스코프가 아니라 상위 즉, 바깥에 있는 환경을 참조하기 때문에 outer라는 단어가 붙은 것이다.
만약 함수 A라는 실행 컨텍스트가 구성될 때 변수의 값을 찾지 못하는 경우, 함수 A를 둘러싼 함수 B에서 찾거나, 함수 B에도 없는 경우 전역 컨텍스트에서 찾는 방식으로 체이닝을 진행하게 된다.
함수 A 범위에 변수 값이 없다면 → 함수 B 스코프에도 변수 값이 없다면 → 전역
자바스크립트 메서드 체이닝의 체이닝과 비슷한 개념이다. 계속 바깥으로 거슬러 올라간다고 보면 된다. 중요한 것은 안쪽으로 거슬러 올라갈 수는 없고 바깥으로만 참조할 수 있다는 것이다.
반대로 현재 실행 컨텍스트 안에 변수의 값을 찾았다면 바깥으로 거슬러 올라가지 않는다. 만약 같은 이름의 식별자가 있다면 현재 실행 컨텍스트의 값만 적용되고, 외부의 것은 적용되지 않는다. 동일한 식별자의 이름이 가려진다는 의미에서 '식별자 쉐도잉(variable shadowing)' 현상이라고 부른다.
지금까지 실행 컨텍스트가 어떻게 구성되어 있는지 살펴봤다. 이제 이 법칙들을 통해 실행 컨텍스트가 어떤 순서로 자바스크립트 코드를 실행하는지 예시를 통해 알아보자.


출처: 코어 자바스크립트 (인프런 강의)
(*상기 이미지 내의 번호와 하위 설명의 번호는 대응되지 않음)
코드가 실행되면 콜 스택에 전역 컨텍스트가 생성된다.
콜 스택은 자바스크립트의 실행 컨텍스트들이 담기는 스택 자료구조다.
전역 컨텍스트 안에는 선언된 식별자들이 수집된다.
outer 함수가 호출되면 outer 함수의 실행 컨텍스트가 열린다.
outer 함수 내에서도 2번과 동일하게 식별자를 수집하는 과정이 진행된다.
만약 outer 함수 내에 변수 값이 없다면 바깥 스코프에서 값을 찾는다.
다음으로 inner 함수의 실행 컨텍스트가 열린다.
inner 함수에서도 outer 함수와 같은 동작이 실행된다.
inner 함수가 호출된다.
이때 var a = 3; 이 호이스팅 되면서 var a = undefined;로 초기화된다.
따라서 inner 함수 내부의 console.log(a)는 undefined가 출력된다.
inner 함수의 실행 컨텍스트가 종료된다.
스택 자료구조는 '후입선출'이므로, inner 함수의 실행 컨텍스트가 먼저 제거된다.
console.log(a)가 실행된다.
outer 함수에 a의 값이 없으므로 전역 컨텍스트의 a를 참조하여 값을 출력한다.
outer() 실행 컨텍스트가 종료되고 스택에서 제거된다.
console.log(a)를 실행한다.
전역에서 a의 값을 찾아 출력한다.
전역 컨텍스트도 종료된 후 제거된다. (자바스크립트 코드 전체 종료)
실행 결과는 다음과 같다.
1
undefined
1
1
자바스크립트의 기본 개념이지만 언제나 무덤과도 같았던 실행 컨텍스트를 드디어 이해하게 되었다. 여러 자료를 찾아보니 영어로 된 개발 용어를 번역하는 과정에서 오해를 불러일으키는 경우도 많았다. 우선 단어의 뉘앙스부터 정확하게 파악하면 이해하는 데 걸리는 시간을 줄일 수 있는 것 같다.
자료를 정리하면서 내가 이해한 바로는, 실행 컨텍스트란 자바스크립트가 코드를 실행하기 위해 미리 등록해두는 사전(환경)과도 같은 것이다. 그 환경 속에서 자바스크립트는 실행 대상과 실행 순서를 결정하게 된다.
사실 이번 포스팅에서 실행 컨텍스트에서 thisbinding 부분은 빠졌다.
다음 포스팅에서 다뤄보도록 하겠다.