Table of Contents
실행 컨텍스트
- 실행 컨텍스트를 바르게 이해하면 자바스크립트가 스코프를 기반으로 식별자와 식별자에 바인딩된 값을 관리하는 방식과, 호이스팅이 발생하는 이유, 클로저의 동작 방식, 그리고 태스크 큐와 함께 동작하는 이벤트 핸들러와 비동기 처리의 동작방식을 이해할 수 있다
- 모든 소스코드는 실행에 앞서 평가 과정을 거치며 코드를 실행하기 위한 준비를 한다. 다시 말해, 자바스크립트 엔진은 소스코드를 소스코드의 평가와 소스코드의 실행 과정으로 나누어 처리한다
- 소스코드 평가 과정이 끝나면 비로소 선언문을 제외한 소스코드가 순차적으로 실행되기 시작한다. 즉 런타임이 시작된다.
- 이때 소스코드 실행에 필요한 정보, 즉 변수나 함수의 참조를 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득한다
실행 컨텍스트의 역할
- 실행 컨텍스트는 소스코드를 실행하는데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다
- 좀 더 구체적으로 말해, 실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다
- 식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다
실행 컨텍스트의 구성요소
실행 컨텍스트 스택
- 실행 컨텍스트 스택은 코드의 실행 순서를 관리한다
- 소스코드가 평가되면 실행 컨텍스트가 생성되고 스택의 최상위에 쌓인다
- 실행 컨텍스트 스택의 최상위에 존재하는 실행 컨텍스트는 언제나 현재 실행 중인 코드의 실행 컨텍스트다
- (함수 호출 스택과 원리가 비슷함)
렉시컬 환경
- 렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조이다
클로저
- 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다
렉시컬 환경
- 렉시컬 스코프는 상위 스코프에 대한 참조가 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다
- (자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프 방식을 따른다)
- 함수는 자신의 내부 슬롯
[[Environment]]
에 자신이 정의된 상위 스코프의 참조를 저장한다 - 이때
[[Environment]]
에 저장된 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다
클로저와 렉시컬 환경
const x = 1;
function outer() {
const x = 10;
const inner = function () { console.log(x); }
return inner
}
const innerFunc = outer();
innerFunc();
outer
함수를 호출하면,outer
함수는 중첩 함수inner
를 반환하고outer
함수의 실행이 종료되고outer
함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거된다- 실행 컨텍스트가 제거되었으므로
outer
함수의 지역 변수x
또한 생명 주기를 마감하기 때문에,x
변수에 접근할 수 있는 방법이 없어보인다 - 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다
inner
함수는 자신이 평가될 때 자신이 정의된 위치에 의해 결정된 상위 스코프를[[Environment]]
에 저장한다outer
함수의 실행 컨텍스트가 스택에서 제거되더라도,outer
함수의 렉시컬 환경은 소멸되지 않는다outer
함수의 렉시컬 환경은inner
함수의[[Environment]]
에 의해 참조되고 있고inner
함수는innerFunc
에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 되지 않는다- 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저다. 하지만 일반적으로 모든 함수를 클로저라고 하지는 않는다
- 외부함수보다 중첩 함수의 생명주기가 더 긴 경우에만 중첩 함수를 클로저라고 한다
- 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고, 중첩 함수가 외부 함수보다 더 오래 유지되는 경우 중첩 함수를 클로저라 한다
클로저의 활용
- 클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다
- 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다
const increase = (function () {
// 카운트 상태 변수
let num = 0;
// 클로저
return function () {
return ++num;
};
}());