클로저(closure)는 JavaScript에서 중요한 개념 중 하나로 자바스크립트에 관심을 가지고 있다면 한번쯤 들어보았을 내용이다. execution context(실행 컨텍스트)
에 대한 사전 지식이 있다면 이해하기 어렵지 않은 개념이다. 클로저는 JavaScript 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(Functional Programming language)에서 사용되는 중요한 특성이다.
클로저는 JavaScript 고유의 개념이 아니므로 ECMAScript 명세에 클로저의 정의가 등장하지 않는다. 클로저에 대해 MDN
은 아래와 같이 정의하고 있다.
“A closure is the combination of a function and the lexical environment within which that function was declared” 클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.
무슨 의미인지 잘 와닿지 않는다. 위 정의에서 중요한 키워드는 “함수가 선언됐을 때의 렉시컬 환경” 이다. 말이 무척이나 난해하니 우선 예제부터 살펴보자
function outerFunc() {
var x = 10; // (2)
var innerFunc = function() { console.log(x) }; // (1)
innerFunc();
}
outerFunc(); // 10
함수 outerFunc 내에서 내부함수 innerFunc가 선언되고 호출되었다. 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFunc의 내부에 선언되었기 때문이다.
<aside>
💡 스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다.
이를 렉시컬 스코핑(Lexical scoping)
이라 한다.
위 예제의 함수 innerFunc는 함수 outerFunc의 내부에서 선언되었기 때문에 함수 innerFunc의 상위 스코프는 함수 outerFunc이다. 함수 outerFunc이 전역에 선언되었다면 함수 innerFunc의 상위 스코프는 전역 스코프가 된다.
</aside>
함수 innerFunc이 함수 outerFunc의 내부에 선언된 내부함수 이므로 함수 innerFunc는 자신이 속한 렉시컬 스코프(전역, 함수 outerFunc, 자신의 스코프)를 참조할 수 있다. 이것을 실행 컨텍스트의 관점에서 설명해보자.
내부함수 innerFunc가 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고 변수 객체(Variable Object)
와 스코프 체인(Scope Chain)
그리고 this
에 바인딩할 객체가 결정된다.
이때 스코프 체인은 전역 스코프를 가리키는 전역 객체와 함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체(Activation object) 그리고 함수 자신의 스코프를 가리키는 활성 객체를 순차적으로 바인딩한다. 스코프 체인이 바인딩한 객체가 바로 렉시컬 스코프의 실체이다.
내부함수 innerFunc가 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있는 것, 다시 말해 상위 스코프에 접근할 수 있는 것은 렉시컬 스코프의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색하였기에 가능한 것이다.