스코프는 자바스크립트 엔진이 식별자를 검색하는 규칙이자 식별자가 유효한 범위다.
스코프 내에서 식별자는 유일해야 한다.
스코프에는 전역 스코프, 지역 스코프가 있다.
전역은 코드 가장 바깥 영역이다.
전역은 전역 스코프를 생성한다.
전역 변수는 전역 스코프에 선언된 변수다. 전역 변수는 어디서든 참조 가능하다.
지역 스코프는 함수 몸체 내부 영역이다.
지역은 지역 스코프를 생성한다.
지역 변수는 지역 스코프에 선언된 변수다. 지역 변수는 지역 변수가 선언된 지역 스코프와 그 하위 스코프에서 참조 가능하다.
스코프 체인은 스코프가 계층적 구조로 연결된 것이다.
변수를 참조할 때, 변수를 참조하려는 코드의 스코프에서 시작하여 상위 스코프를 살펴보며 찾는다.
상위 스코프에 선언된 변수는 하위 스코프에서 참조 가능하지만, 하위 스코프에 선언된 변수는 상위 스코프에서 참조 불가능하다.
function foo() {
console.log('global function foo');
}
function bar() {
function foo() {
console.log('local function foo');
}
foo();
}
bar();
함수 선언문으로 함수를 정의하면 런타임 이전에 자바스크립트 엔진이 함수 정의를 먼저 실행한다.
함수 객체를 생성하고 함수 이름과 동일한 식별자를 생성해서 바인딩한다.
따라서 함수 식별자가 존재하므로, 함수도 변수를 검색하는 것과 똑같이 검색한다.
지역은 함수 몸체 내부다
함수 몸체 내부가 지역이기 때문에 함수에 의해서만 지역 스코프가 생성된다.
이러한 특성을 함수 레벨 스코프라고 한다.
var
변수는 함수 레벨 스코프를 따른다.
지역은 블록 내부다
let, const
변수는 모든 코드 블록을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
함수를 어디서 정의했는지에 따라 상위 스코프가 결정된다.
함수가 호출된 위치는 상위 스코프 결정에 아무 영향이 없다.
함수의 상위 스코프는 함수 정의(함수 선언문, 함수 표현식)가 실행될 때 결정되며, 함수 정의가 실행되어 생성된 함수 객체는 이렇게 결정된 상위 스코프를 기억한다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
실행 컨텍스트는 소스 코드를 실행하는데 필요한 환경을 관리하는 영역이다.
식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로, 코드 실행 순서는 실행 컨택스트 스택(콜 스택)으로 관리한다.
ECMAScript 사양에서는 소스코드를 4가지 타입으로 나눈다.
모든 소스코드는 실행 전에 평가 과정을 거친 후 실행한다.
소스코드 평가 과정(선언문만 먼저 실행)에서 실행 컨텍스트를 생성한다.
평가 과정이 끝난 후 선언문을 제외한 소스코드를 실행한다.
실행 컨텍스트 스택을 통해 코드 실행 순서를 관리한다.
먼저 전역 코드를 평가하여 전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택(콜 스택)에 '푸시'한다.
함수가 호출되면 함수 코드가 평가되어 호출된 함수의 실행 컨텍스트를 생성하고 콜 스택에 푸시한다.
함수가 종료되면 해당 실행 컨텍스트는 콜 스택에서 '팝'된다.
전역 코드에 실행할 코드가 남지 않으면 전역 실행 컨텍스트도 콜 스택에서 팝된다.
렉시컬 환경은 실행 컨텍스트를 구성하는 컴포넌트다. 렉시컬 환경은 스코프를 구분하여 관리한다.
소스 코드 평가가 완료되면 실행 컨텍스트 또한 완성된다.
1. 전역 객체 생성
전역 코드 평가 이전에 전역 객체를 생성한다.
전역 객체에는 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체, 특정 환경을 위한 호스트 객체를 포함한다.
전역 객체도 Object.prototype
을 상속한다.
2. 전역 실행 컨텍스트(Global Execution Context) 생성
각 실행 컨텍스트(
Execution Context
)는 렉시컬 환경(LexicalEnvironment
)을 포함한다.
Global Execution Context |
---|
LexicalEnvironment |
전역 실행 컨텍스트(Global Execution Context
)를 생성하여 콜 스택에 푸시한다.
3. 전역 렉시컬 환경(Global Lexical Environment) 생성
각 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경 참조로 구성된다.
전역 실행 컨텍스트(Global Execution Context
)의 렉시컬 환경(LexicalEnvironment
) 컴포넌트는 Global Lexical Environment
를 가리킨다.
전역 렉시컬 환경(Global Lexical Environment
)은
전역 환경 레코드(GlobalEnvironmentRecord
)와 외부 렉시컬 환경 참조(OuterLexicalEnvironmentRefrence
)로 구성된다.
Global Lexical Environment |
---|
GlobalEnvironmentRecord |
OuterLexicalEnvironmentRefrence |
4. 객체 환경 레코드(Object Environment Record)와 선언적 환경 레코드(Declarative Environment Recode)
전역 환경 레코드(GlobalEnvironmentRecord
)는
객체 환경 레코드(Object Environment Record
), 선언적 환경 레코드(Declarative Environment Record
), this
바인딩으로 구성된다.
Object Environment Record |
---|
BindingObject |
...
Declarative Environment Record |
---|
let, const로 선언한 식별자와 값 |
BindingObejct
는 전역 객체를 가리킨다.
var
전역 변수와 함수 선언문으로 정의된 전역 함수는 전역 객체의 프로퍼티로 등록된다.
전역에서 식별자 검색은 저 두 곳에서 찾는다.
5. this
바인딩
전역 환경 레코드 [[GlobalThisValue]]
내부 슬롯에 this
가 바인딩된다.
전역 코드의 this
는 전역 객체이므로 this
를 호출하면 전역 객체가 반환된다.
6. 외부 렉시컬 환경 참조 결정
전역 환경 렉시컬의 외부 렉시컬 환경은 없기 때문에 OuterLexicalEnvironmentReference
는 null
이다.
얘도 비슷하다.
foo Excution Context |
---|
LexicalEnvironment |
렉시컬 환경은 함수 렉시컬 환경(foo Lexcial Environment
)을 가리킨다.
foo Lexical Environment |
---|
FunctionEnvironmentRecord |
OuterLexcialEnvironmentReference |
함수 렉시컬 환경은 함수 환경 레코드와 외부 렉시컬 환경 참조로 구성된다.
함수 환경 레코드(FunctionEnvironmentRecord
)는
Function Evironment Record
와 this
바인딩으로 구성된다.
Function Environment Record |
---|
함수 코드에서 선언한 변수 및 함수들 |
this
바인딩은 동적으로 결정된다.
FunctionEnvironmentRecord
의 [[ThisValue]]
내부 슬롯에 this
가 바인딩된다.
외부 함수보다 함수 내부에 정의된 중첩 함수를 반환하고,
이 중첩함수가 더 오래 유지(생존)되어 이미 종료된 외부 함수(상위 스코프)의 식별자를 참조하는(값 변경도 가능) 경우,
이 중첩 함수를 클로저라고 한다.
클로저는 상태가 의도치 않게 변경되지 않도록 은닉하고, 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.
함수 객체는 내부 슬롯
[[Environment]]
에 자신의 상위 스코프의 참조를 저장한다.
상위 스코프는 함수 정의가 평가되어 함수가 생성될 때, 실행중인 실행 컨텍스트의 렉시컬 환경이다. 이를 함수 객체의 [[Environment]]
내부 슬롯에 저장한다.
이렇게 저장된 상위 스코프(렉시컬 환경)는 반환된 클로저가 유지되는 한 제거되지 않고 유지된다.
즉, 상위 함수가 종료되어 실행 컨텍스트가 콜 스택에서 제거되어도, 상위 함수의 렉시컬 환경은 메모리에 계속 남아있는 것이다.
외부 함수보다 오래 생존하는 중첩함수라 하더라도, 상위 스코프의 식별자를 참조하지 않는 함수는 클로저가 아니다.
중첩함수가 상위 스코프의 식별자를 참조하기는 하지만,
중첩함수가 반환되어 생명주기가 외부 함수보다 더 긴 경우가 아니라면,
이 중첩함수도 일반적으로는 클로저가 아니라고 한다.
중첩함수가 외부함수로부터 반환되어 외부 함수보다 더 오래 유지되며,
외부함수의 식별자도 참조하지만,
외부함수의 모든 식별자를 참조하는게 아닌 경우 모던 브라우저는 최적화를 통해클로저가 참조하고 있는 상위 스코프의 식별자만을 기억한다.
클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수라고 한다.