클로저는 자바스크립트 고유의 개념이 아니다. 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
클로저는 자바스크립트 고유 개념이 아니므로, 클로저에 관한 정의가 ECMAScript 사양에 등장하지 않는다.
MDN에서는 클로저를 다음과 같이 정의한다.
A clousure is the combination of a function and the lexical environment within which that function was declared.
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이고 즉 함수가 선언된 렉시컬 환경에 대해 먼저 이해할 필요가 있다.
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
}
innerFunc()
}
outerFunc();
outerFunc 함수 내부에서 중첩 함수 innerFunc가 정의되고 호출되었다.
이때 중첩 함수 innerFunc의 상위 스코프는 외부 함수 outerFunc의 스코프이다.
따라서 중첩 함수 innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFunc의 x 변수에 접근할 수 있다.
그러나 만약 outerFunc 밖에서 즉, 함수 내부에 정의된 중첩 함수가 아니라면 어떨까?
함수 호출부가 outerFunc안에 있다 하더라도 outerFunc 함수 변수에 접근할 수 없고 x는 값 1을 가지고 있을 것이다.
이 같은 현상이 발생하는 이유는 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문이다.
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프)라 한다.
const x= 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
위 예제를 살펴보면, foo함수, bar 함수 상위 스코프는 전역이다. 당연한 함수를 어디서 호출하는 지는 함수의 상위 스코프랑은 관계가 없고
즉, 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않는다.
스코프의 실체는 실행 컨텍스트의 렉시컬 환경이다. 이 렉시컬 환경은 자신의 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결된다.
이것이 바로 스코프 체인이다.
정리하자면,
함수의 상위 스코프를 결정한다 ⇢ 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다
외부 렉시컬 환경에 대한 참조에 저장할 참조 값 ⇢ 상위 렉시컬 환경에 대한 참조 ⇢ 상위 스코프
렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다. 이것이 바로 렉시컬 스코프다.
함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.