[코어 자바스크립트] 02. 실행 컨텍스트
이번 글은 코어 자바스크립트 책의 2장(실행 컨텍스트)을 공부한 내용이다.
자바스크립트 엔진이 코드를 처리하는 과정을 통해 코드 실행 순서를 이해하고, 동적 언어로서의 성격을 파악할 수 있었다. 🤟
실행 컨텍스트란?
-
실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택(call stack)에 쌓아올렸다가, 가장 위에 쌓여잇는 컨텍스트와 관련있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.
-
실행 컨텍스트는 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등이 있다.
우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것뿐이다.
전역 컨텍스트라는 개념은 일반적인 실행 컨텍스트와 특별히 다를 것 없다. 최상단의 공간은 코드 내부에서 별도의 실행 명령없이 브라우저에서 자동으로 실행하므로, 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 된다.
-
실행 컨텍스트 객체는 활성화되는 시점에
VariableEnvironment
,LexicalEnvironment
,ThisBinding
의 세 가지 정보를 수집한다.
실행 컨텍스트 객체
-
실행 컨텍스트를 생성할 때는 VariableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만, LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지한다.
-
VariableEnvironment와 LexicalEnvironment는
environmentRecord
와outerEnvironmentReference
로 구성된다.
environmentRecord
-
매개변수명, 변수의 식별자, 선언한 함수의 함수명등 현재 컨텍스트와 관련된 코드의 식별자 정보들을 수집해 저장한다.
변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태이다.
-
호이스팅(hoisting)은 이런 수집 과정을 추상화한 개념으로, 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 수집한 정보를 ‘끌어올린다’고 해석하는 것이다.
-
변수 선언과 값 할당이 동시에 이뤄진 문장은 선언부만 호이스팅하고, 할당 과정은 원래 자리에 남게 된다.
-
이런 특징 때문에 함수 선언문과 함수 표현식의 차이가 발생한다.
함수 선언문, 함수 표현식
// 함수 선언문
// 함수명 a가 곧 변수명이다.
function a() {
/* ... */
}
a();
// 익명 함수 표현식
// 변수명 b가 곧 함수명이다.
var b = function () {
/* ... */
};
b();
// 기명 함수 표현식
// 변수명은 c, 함수명은 d이다.
var c = function d() {
/* ... */
};
c();
d(); // ERROR!!
함수명 d로 함수를 실행하면 에러가 발생한다. 외부에서는 기명 함수식의 함수명으로 함수를 호출할 수 없기 때문이다. 함수명은 오직 함수 내부에서만 접근할 수 있다. (즉, 재귀 함수 호출은 가능하다.)
과거에는 기명 함수 표현식은 함수명이 잘 출력됐지만 익명 함수 표현식은 undefined 또는 unnamed 값이 나와서 디버깅을 하기 어려웠다. 하지만 최근 모든 브라우저들은 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당하기 때문에 굳이 기명 함수 표현식을 사용할 필요가 없다.
outerEnvironmentReference
-
해당 함수가 선언된 위치의 LexicalEnvironment를 참조한다.
var a = 1; var outer = function () { var inner = function () { console.log(a); // undefined 출력 (a가 LexicalEnvironment에서 발견되지만, 값이 할당되기 전) var a = 3; }; inner(); console.log(a); // 1 출력 (outerEnvironmentReference에서 발견한 값 출력) }; outer(); console.log(a); // 1 출력 (LexicalEnvironment에서 발견한 값 출력)
이런 경우,
- 전역 컨텍스트
- LexicalEnvironment : { a, outer }
- outerEnvironmentReference : X
→ 전역 컨텍스트는 선언 시점이 없으므로
- outer
- LexicalEnvironment : { inner }
- outerEnvironmentReference : [ GLOBAL, { a, outer } ]
→ 전역 객체의 LexicalEnvironment
- inner
- LexicalEnvironment : { a }
- outerEnvironmentReference : [ outer, { inner } ]
→ outer의 LexicalEnvironment
- 전역 컨텍스트
-
이처럼 연결리스트(linked list) 형태를 띠기때문에 가장 가까운 요소부터 차례대로만 접근할 수 있고 다른 순서로 접근하는 것은 불가능하다.
-
이런 구조적 특징으로 여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다.
스코프, 스코프 체인
-
스코프(scope)는 변수의 유효범위를 말한다.
ES5까지의 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 발생한다.
ES6에서는 블록에 의해서도 스코프 경계가 발생하게 해 다른 언어와 비슷해졌다. 다만, var이 아닌 let과 const, class, strict mode에서의 함수 선언 등에 대해서만 범위로서의 역할을 수행한다. ES6에서는 함수 스코프, 블록 스코프로 구분해 용어를 사용한다. -
코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고, 발견하지 못할 경우 다시 outerEnvironmentRecord에 담긴 LexicalEnvironment를 탐색하는 과정을 거친다.
이렇게 안에서 바깥으로 차례로 검색해 나가는 것을 스코프 체인(scope chain)이라고 한다.
전역 컨텍스트의 LexicalEnvironment까지 탐색해도 해당 변수를 찾지 못하면 undefined를 반환한다.
- 전역변수 : 전역 컨텍스트의 LexicalEnvironment에 담긴 변수
- 지역변수 : 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수
- 전역변수 : 전역 컨텍스트의 LexicalEnvironment에 담긴 변수
ThisBinding
- 실행 컨텍스트를 활성화하는 당시에 지정된
this
가 저장된다. - 함수를 호출하는 방법에 따라 값이 달라지며, 지정되지 않을 경우 전역 객체가 저장된다.