실행 콘텍스트는 자바스크립트의 동작 원리를 담고 있는 핵심 개념이다.
실행 콘텍스트는 ECMAScript 사양에서 사용하는 어려운 용어가 다수 등장해 초기 실행 콘텍스트를 이해하려 해 봤지만 실패했던 기억이 있다. 따라서 좀 더 쉽게 이해하기 위해 책을 구매했고 해당 내용을 정리해보려고 한다.
1. ECMASCript code
자바스크립트 스펙에서는 소스코드를 4가지로 분류하였다. 이러한 4가지 타입의 소스코드는 실행 콘텍스트를 생성한다.
- 전역 코드
- 전역에 존재하는 소스코드를 말하며 이는 전역 실행 컨텍스트를 생성한다. 전역 코드는 전역 변수를 관리하기 위해 최상위 스코프인 전역 스코프를 생성해야 하며, var 키워드 전역 변수, 함수 선언문으로 정의된 전역 함수를 전역 객체의 프로퍼티와 메서드로 바인딩하고 참조하기 위해 전역 객체와 변수, 함수가 연결되어야 한다. - 함수 코드
- 함수 코드는 지역 스코프를 생성하고 지역 변수, 매개변수, arguments 객체를 관리해야 한다. 그리고 생성한 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결해야 한다. - eval 코드
- strict mode에서 독자적인 스코프를 생성한다. - 모듈 코드
- 모듈 코드는 모듈별로 독립적인 모듈 스코프를 생성한다.
평가
자바스크립트는 소스코드를 실행하기전 평가 과정을 거쳐 실행을 위한 준비를 한다.
따라서 이 실행 컨텍스트는 평가과정에서 생성된 후 실행의 결과를 실행 콘텍스트에 저장한다고 생각할 수 있다.
실제로 평가 과정에서는 실행 콘텍스트 생성, 변수, 함수 등의 선언문만 먼저 실행하여 실행 콘텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다.
따라서 평가과정에서 선언문을 실행하고 이에 대한 결과로 스코프에 식별자가 등록된다. 이후 실행을 한다.
왜 호이스팅이 발생하는 이유를 깨달았다. 호이 스팅은 그 범위에 따라 선언을 분리하고 선언을 항상 최상위로 끌어올리는 것을 의미한다. 평가 과정에서 선언문을 먼저 실행하여 실행 콘텍스트의 환경 레코드에 등록하기 때문이다.
기존 let, const 키워드는 호이 스팅 되지 않는 줄 알았지만 곰곰이 생각해보면 평가과정에서 const, let, var모두 선언문이라면 호이 스팅도 평가되는 것을 알 수 있다. 다만 var로 선언된 경우 해당 식별자는 평가과정에서 undefined로 초기화되지만 let, const는 평가 이후 실행 과정에서 값이 할당되기 때문에 할당 전에 참조 시 에러가 발생하는 것이었다.!
* 함수 코드 평가
함수 내부에 진입 전 함수 코드에 대한 평가를 통해 함수 실행 콘텍스트를 만드는 과정에서 매개변수와 지역 변수가 먼저 실행되고 그 결과 생성된 매개변수, 지역 변수가 지역 스코프에 등록된다.
이 과정에서 생각해 보야할 것은 자바스크립트에서 함수는 1급 시민이다. 즉 인자로 함수를 넘기는 경우 인자의 함수 실행 콘텍스트가 먼저 생성되고 값을 이미 등록한다는 점이다. 그렇다면 함수에 함수를 리턴하도록 하고 실제 요청을 받았을 때 lazy 하게 실행하도록 코드를 작성할 수도 있지 않을까?라고 상상해본다.
자바스크립트에서 코드가 실행되려면 스코프, 식별자, 코드 실행 순서의 관리가 필요하다.
이때 코드 실행 순서의 관리란 순차적으로 실행되다 함수를 만나면 해당 함수로 진입한다. 만약 함수가 return을 만나면 다시 함수 진입점으로 돌아가야 하는데 이를 실행 컨텍스트가 관리한다.
실행 콘텍스트는 결국 소스코드를 실행하는 데 필요한 환경을 제공하고, 코드의 실행 결과를 관리하는 영역이다.
(식별자를 등록, 관리하며, 스코프와 코드 실행 순서를 구현한 내부 메커니즘)
이 중 스코프와 식별자는 실행 콘텍스트의 렉시컬 환경으로 관리하고 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다.
중간 정리
- 자바스크립트 엔진은 전역 코드를 시작점으로 잡는다.
- 소스코드는 평가과정과 실행 과정으로 분리되어있다.
- 평가 과정에서 선별된 식별자들은 소스코드 타입의 실행 콘텍스트에 (등록, 관리)되고 스코프 체인을 구성한다.
- 함수를 만나면 코드 실행의 제어권을 넘긴다. 코드 실행 순서는 실행 콘텍스트의 스택으로 관리한다.
console.log()도 함수라서 코드 제어권을 넘기고 log()의 실행 콘텍스트를 생성한다. -이해가 쉬워진다.
렉시컬 환경
Lexcial Enviroment는 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 콘텍스트를 구성하는 컴포넌트이다.
렉시컬 환경은 키와 값을 가지는 객체 형태의 스코프를 생성하여 관리한다.
책에서는 strict mode, eval, try/catch를 제외하고 LexcialEnviroment와 VariableEnvironment 컴포넌트를 구분하지 않고 렉시컬 환경으로 통일하여 설명하기 때문에 예외 경우는 별도로 찾아서 정리하자.
예외를 제외한 렉시컬 환경은 다음과 구성되어있다.
| Lexical Environment | |
| Environment Recode | ... |
| OuterLexcialEnvironment Reference | ... |
- Environment Recode( 환경 레코드)
스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소이며, 소스코드의 타입에 따라 관리하는 내용에 차이가 존재한다. - Outer Lexical Environment Reference ( 외부 렉시컬 환경에 대한 참조)
외부 렉시컬 환경에 대한 참조는 상위 스코프를 가리킨다. 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 상위 코드의 렉시컬 환경을 의미한다. 이를 통해 단방향 링크드 리스트인 스코프 체인을 구현한다.
* 전역객체
전역 객체는 전역 코드 평가 이전에 생성된다. 이때 전역 객체에는 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체가 추가되며, 환경에 따라 특정 객체를 포함하게 된다. 전역 객체도 Object.prototype을 상속받기에 전역 객체도 프로토타입 체인의 일원이다.
전역 코드 평가 순서
소스코드 로드 시 자바스크립트 엔진은 전역 코드를 평가하며 다음과 같은 평가 순서를 갖는다.
- 전역 실행 컨텍스트 생성
- 전역 렉시컬 환경 생성
- 전역 환경 레코드 생성
- 객체 환경 레코드 생성
- 선언적 환경 레코드 생성
- this 바인딩
- 외부 렉시컬 환경에 대한 참조 결정
- 전역 환경 레코드 생성
1) 전역 실행 컨텍스트 생성
비어있는 실행 컨텍스트를 생성하여 실행 컨텍스트에 푸시한다. 스택 최상단에는 전역 실행 컨텍스트가 running execution context가 된다.
2) 전역 렉시컬 환경 생성
Global Lexical Environment를 생성하고 전역 실행 컨텍스트와 연결한다.
전역 렉시컬 환경에는 특별한 상황을 전부 제외한다고 가정하고 2개의 컴포넌트로 구성되어있는데,
첫 번째는 환경 레코드 두 번째는 외부 렉시컬 환경에 대한 참조이다.
2.1) 전역 환경 레코드 생성
환경 레코드 중 전역 렉시컬 환경을 구성하는 컴포넌트를 전역 환경 레코드라 하는 것 같다.
이 전역 환경 레코드는 전역 변수를 관리하는 전역 스코프, 전역 객체의 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 제공한다.
모든 전역 변수가 전역 객체의 프로퍼티가 되는 ES6 이전에는 전역 객체가 전역 환경 레코드의 역할을 수행했다고 한다.
다만 let, const로 선언된 지역 변수는 전역 객체의 프로퍼티가 아닌 개념적인 블록 내에 존재한다.
var 키워드와 let, const 키워드로 선언한 전역 변수를 구분하여 관리하기 위해 전역 스코프 역할을 하는 전역 환경 레코드는 객체 환경 레코드(Object Environment Record)와 선언적 환경 레코드(Declarative Environment Record)로 구성되어 있다.
| 전역 실행 컨텍스트 | ||
| 전역 렉시컬 환경 | ||
| 전역 환경 레코드 | 외부 렉시컬 환경 참조 | |
| 객체 환경 레코드 | 선언적 환경 레코드 | |
- 그림으로 상상을 더 쉽게 해 보자.
2.1.1) 객체 환경 레코드
객체 환경 레코드는 기존의 전역 객체가 관리하던 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 관리한다.
전역 환경 레코드를 구성하는 객체 환경 레코드는 BindingObject라고 부르는 객체와 연결된다. 이 바인딩 오브젝트는 전역 객체가 생성되는 단계에서 생성된 전역 객체이다.
전역 코드 평가 과정에서 var 키워드로 선언한 변수와 함수 선언문으로 정의된 전역 함수는 전역 환경 레코드의 객체 환경 레코드에 연결된 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 된다.
(음.. 전역 코드 평가전에 전역 객체가 생성되고 var키워드 function xxx.. 등 선언문으로 생성된 함수들이 BindingObject의 어떠한 행위를 통해 전역 객체에 바인딩이 된다는 말인 것 같다.)
그리고 이때 등록된 식별자를 객체 환경 레코드에서 검색하면 전역 객체의 프로퍼티를 검색하여 반환한다.
( 전역 객체에 식별자가 바인딩되었는데 전역 객체에서 검색하지 않고 객체 환경 레코드를 통해 검색한다?)
이러한 과정을 통하기 때문에 전역 객체를 가리키는 식별자(window) 없이 전역 객체의 프로퍼티를 참조할 수 있는 메커니즘이다.
function foo(a) {}
const a = 1;
var b = 2;
객체 환경 레코드에는 var b와 foo() 함수가 전역 객체의 프로퍼티와 메서드가 된다.
이 과정에서 var 키워드로 선언한 변수는 암묵적으로 평가 단계에서 undefined로 초기화되어 선언문 이전에도 참조하여도 에러가 발생하지 않고 함수 선언문도 호이 스팅 되지만 함수 이름과 동일한 이름의 식별자를 객체 환경 레코드에 바인딩된 BindingObject를 통해 키로 등록하고 즉시 함수 객체를 할당한다.
따라서 함수 선언문 이전에 호출할 수 있다.
2.1.2) 선언적 환경 레코드
선언적 환경 레코드는 let, const 키워드로 선언한 전역 변수를 관리한다.
let, const 키워드로 선언한 변수와 let, const키워드로 변수에 할당한 함수 표현식들은 선언적 환경 레코드에 등록되고 관리된다.
앞에서 let, const를 사용한 변수들은 전역 객체의 프로퍼티로 바인딩되지 않고 개념적인 블록 내에 존재한다고 했는데, 이는 바로 선언적 환경 레코드를 의미한다.
또한 const 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행한다.
초기화 단계, 즉 런타임에 실행 흐름이 const로 선언한 변수의 선언문에 도달하기 전까지 TDZ(Temporal Dead Zone)에 빠지게 된다.
let foo = 1;
{
console.log(foo) //error
let foo =2;
}
블록에 실행 흐름이 진입했을 때 평가를 시작할 것이고 foo변수는 호이스팅될 것이다. 이 과정에서 할당되기 전에 참조하기 때문에 에러가 발생한다.
즉 객체 환경 레코드와 선언적 환경 레코드는 서로 협력하여 전역 스코프와 전역 객체를 관리한다.
2.2) this 바인딩
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩된다. 보통 전역 코드에서 this를 참조하면 전역 객체가 반환되는데, 이때 전역 환경 레코드의 [[GlobalThisvalue]] 내부 슬록에 전역 객체가 바인딩되어있기 때문이다.
this 바인딩은 전역 환경 레코드와 함수 환경 레코드에만 존재한다.
2.3) 외부 렉시컬 환경에 대한 참조 결정
Outer Lexcival Environment Reference는 현재 평가 중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경, 즉 상위 스코프를 가리킨다. (스코프 체인이 소스코드 평가과정에서 정해지는구나)
현재 전역 소스코드의 실행 과정인데, 전역 코드를 포함하는 소스코드는 없으므로 이 경우 외부 렉시컬 환경에 대한 참조 결정에는 null이 할당된다. 즉 전역 렉시컬 환경이 스코프 체인의 종점이라는 소리이다.
3. 전역 코드 실행
이제 전역 코드의 평가과정이 마무리되어 순차적으로 실행된다. 각 변수 할당 문이 실행되어 값이 할당된다.
--
전역 코드를 실행하는 순서를 하나씩 정리해가면서 머리에 상상이 되기 시작한다.
다음은 가장 중요한 함수 코드를 평가하고 실행하는 순서를 알아보자.
'JavaScript' 카테고리의 다른 글
| 클로저 (0) | 2022.02.24 |
|---|---|
| 함수 코드 실행 컨텍스트 (0) | 2022.02.23 |
| this (0) | 2022.02.04 |
| 함수, 1급 객체 (0) | 2022.01.25 |
| Iteration protocol (0) | 2022.01.17 |
댓글