실행 컨텍스트(execution context)란 자바스크립트 코드를 실행할 때 필요한 정보들을 저장하고 제공하는 환경이다.
- 즉, 스코프의 정보를 담은 환경을 의미한다.
- 현재 실행되고 있는 컨텍스트에서 이 컨텍스트와 관련이 없는 코드가 실행된다면 새로운 컨텍스트가 생성되어 제어권을 가져간다.
- 그리고 이렇게 생성된 컨텍스트들은 실행 컨텍스트 스택안에 쌓이게 된다.
- 각각의 컨텍스트는 실행이 종료된 이후 소멸됨. 소멸된 이 후 이전의 실행 컨텍스트에 제어권을 넘겨주게 된다.
- 전역 실행 컨텍스트까지 실행이 완료되면 모든 실행이 완료된 것으로 볼 수 있다.
console.log('global context')
function foo() {
console.log('foo context');
}
function bar() {
foo();
console.log('bar context');
}
bar();
// 출력 과정
// global context
// foo context
// bar context
해당 코드의 실행 컨텍스트 생성 과정을 그림으로 제작해보았다.
실행 컨텍스트는 두 가지 컴포넌트로 구성된다.
- Lexical Environment (LE)
- Variable Environment (VE)
그리고 이 컴포넌트들은 Environment Records(ER)라 불리는 형태로 구성되어 있다.
function foo() {
const a=1;
var b=2;
console.log(a,b);
}
foo();
위 코드의 실행 컨텍스트 구성 요소를 그림으로 표현해보았다.
foo 실행 컨텍스트의 LE 와 VE는 foo함수 호출에 대한 다양한 정보를 담고 있다.
선언한 식별자와 this 바인딩 그리고 상위 스코프인 전역 실행 컨텍스트의 Global ER을 참조하는 OuterEnv 라는 필드가 있다.
호이스팅이 발생하는 이유는 물리적으로 코드를 위로 끌어올린 것이 아니다. 선언문이 있는 코드라인을 JS 엔진이 먼저 전체코드를 스캔해서 미리 기록해놓기 때문에 발생하는 현상이다. 이 때 기록을 해놓는 곳이 바로 Environment Record 이다. ER 은 식별자와 식별자에 바인딩된 값을 기록해두는 객체다. ER 에 변수가 어떻게 저장되는 지 알면 호이스팅을 더 자세하게 이해할 수 있다.
아래 코드가 실행되기 전의 과정을 살펴보자.
console.log(bar);
var bar = 1;
console.log(bar);
var
로 선언했기 때문에 undefined 로 값을 초기화 해둔다. // ER 내 bar { bar : undefined }
본격적인 실행에 앞서 스캔하고 준비하는 단계를 생성 단계(Creation Phase) 라고 한다. 생성 단계에서는 Execution context 가 생성되고 선언문만 실행해서 ER에 기록한다. 그리고 이어 선언문 외 나머지 코드를 실행하는 실행 단계(Execution Phase)가 있다. 실행 단계에서는 선언문 외 코드를 순차 실행하고 ER을 참조 및 업데이트한다.
선언(Declaration)
메모리 공간을 확보하고 식별자와 연결
초기화(initialization)
식별자에 암묵적으로 undefined 값 바인딩
생성 단계를 마친 후 코드가 실행되는 과정을 알아보자
console.log(bar); <<
// undefined
var bar = 1;
console.log(bar);
스캔을 끝내고 자바스크립트 엔진은 코드를 실행시킨다.먼저 console.log(bar) 를 마주한다. bar를 출력하기 위해서 현재 활성화된 실행 컨텍스트 내에 ER를 보고 이미 기록된 bar 의 값을 참조해서 출력한다.
console.log(bar);
var bar = 1; <<
console.log(bar);
두번째 줄에서는 생성 단계에 선언한 bar 에 바인딩된 값을 1로 업데이트해서 ER 에 기록해둔다.
// ER 내 bar { bar : 1 }
console.log(bar);
var bar = 1;
console.log(bar); <<
// 1
마지막 줄에서는 역시나 자바스크립트 엔진은 ER 의 bar 에 바인딩된 값을 참조해서 bar 의 값을 결정한다.
console.log(bar); <<
// reference error
const bar = 1;
console.log(bar);
만약 변수 선언을 const
나 let
으로 할 경우 자바스크립트 엔진은 bar 식별자를 기록하는 것은 변함 없지만 값을 초기화하지는 않는다.(var
의 경우 undefined
로 초기화)
// ER 내 bar { bar : }
따라서 선언문 이전에 bar 값을 참조하려고 할 경우 reference error
가 발생한다.
이처럼 let
이나 const
로 선언했을 때 식별자를 참조할 수 없는 구역을 바로 일시적 사각지대(Temporal Dead Zone)이라고 한다.
var
로 변수 선언할 경우 선언과 초기화가 동시에 이루어진다.
선언 단계에서는 메모리 공간을 확보해두고 메모리 주소에 식별자를 연결해둔다.
let
const
로 선언하는 경우에는 초기화 없이 선언만 이루어진다.
자바스크립트는 함수를 변수에 담을 수 있다.
bar(); <<
// Type Error
var bar = () => {
console.log('이것 좀 barbar')
}
bar 함수를 var
로 선언할 경우 ER에는 undefined 로 초기화가 이루어진다. 하지만 undefined
는 함수처럼 호출할 수 없기 때문에 타입 에러가 발생한다.
bar(); <<
// reference Error
const bar = () => {
console.log('이것 좀 barbar')
}
const
로 선언할 경우 초기화 자체가 되지 않았기 때문에 호출 시 reference error 가 발생한다.
이처럼 변수에 함수를 담아서 함수를 선언하는 방식을 함수 표현식 (Function Expression) 이라고 한다. 또한 함수를 변수에 담았기 때문에 변수 호이스팅과 똑같이 동작한다.
그렇다면 함수를 변수에 담지 않고 function 으로 선언하면 어떻게 될까?
bar(); <<
// console : 이것 좀 barbar
function bar() {
console.log('이것 좀 barbar')
}
function
으로 함수를 선언하는 경우에는 자바스크립트 엔진이 선언과 동시에 완성된 함수 객체를 생성해서 ER에 기록해둔다.
// ER 내 bar { bar : f{} }
따라서 선언 전에 함수를 호출하더라도 정상적으로 함수가 작동한다. 이처럼 함수를 변수에 담지 않고 function
으로 선언하는 것을 함수 선언문(function declaration) 이라고 한다.
arrow function 과 같은 함수 표현식과 함수 선언문 방식의 가장 큰 차이는 선언 전 호출 유무이다.
여기까지 호이스팅을 통해 자바스크립트 엔진이 실행 컨텍스트 내 환경 레코드 ER 에 값을 저장하는 과정을 알아보았다.
Outer Environment Reference 와 Environment Record 를 합쳐 Lexical Environment 렉시컬 환경 또는 정적 환경이라고 한다.
let lamp = false;
function secondFloor() {
let lamp = true;
console.log(lamp) <<
}
secondFloor();
위 코드를 보자.
자바스크립트 엔진은 선언 단계에서 실행 컨텍스트에 lamp 와 secondfloor 를 선언한다.
ER
{ lamp : , secondfloor : f{} }
그리고 second 함수가 호출되면 새로운 실행 컨텍스트가 생성된다. 그리고 컨텍스트 내 ER 에 lamp 값을 저장한다. 매개변수가 있을 경우 매개변수도 저장한다.
컨텍스트 구조
secondfloor { lamp : } 전역 { lamp : , secondfloor : f{} }
선언 단계가 끝나고 실행 단계에서 자바스크립트 엔진은 secondfloor 내 console.log(lamp) 를 마주하고 어떤 lamp 의 값을 사용할지 결정하는데 이를 식별자 결정(Identifier Resolution) 이라고 한다.
코드를 좀 더 추가해보자.
let lamp = false;
function secondFloor() {
let lamp = true;
thirdfloor();
console.log(lamp)
}
function thirdFloor() {
console.log(lamp)
}
secondFloor();
thirdFloor 함수를 추가하고 해당 함수 내에서 lamp 를 출력하면 어떻게 될까?
우선 새로운 함수가 호출되기 때문에 실행 컨텍스트가 하나 추가된다.
실행 컨텍스트 구조
{} {lamp : ,thirdFloor : f{}} {lamp : ,secondFloor : f{}}
그리고 thirdFloor 실행 컨텍스트 ER 에 lamp 가 존재하지 않기 때문에 Outer 를 이용하여 상위 컨텍스트로 이동한다.그리고 secondFloor 의 실행 컨텍스트에서 찾은 lamp의 값을 참조하여 출력하고 실행을 완료한다. lamp 의 값을 찾았기 때문에 더이상 상위 컨텍스트를 조회하지 않는다. 전역 컨텍스트에 존재하는 동일한 식별자 lamp 는 무시되었다는 얘기다. 이처럼 동일한 식별자로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상을 변수 섀도잉(variable Shadowing) 이라고 한다.
그리고 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 스코프 체인(Scope Chain) 이라고 한다.또한 스코프 체인을 이용하여 값을 찾는 행위를 스코프 체이닝(Scope Chaining)이라고 한다.
실행 컨텍스트란 코드를 실행하는 데 필요한 환경을 제공하는 객체다.
여기서 환경이란 코드 실행에 영향을 주는 조건이나 상태를 말한다.
코드를 실행할 때 식별자 결정을 더욱 효율적으로 하기 위한 수단으로 필요한 정보를 한 데 모아 제공하는 객체라고 정리할 수 있다.
이로써 실행 컨텍스트에 대해 조금은 이해하게 되었다..! 뿌듯..!