클로저는 함수와 해당 함수가 선언된 렉시컬 환경의 조합이다. 외부 함수의 실행이 종료된 후에도 중첩 함수는 외부 함수의 변수를 기억하여 참조할 수 있다. 이러한 중첩 함수를 클로저라고 한다.
📌 렉시컬 스코프
함수가 정의된 위치에 의해 상위 스코프가 결정되는 스코프
함수 객체를 생성할 때 자신이 정의된 위치에 의해 결정된 상위 스코프의 참조를 자신의 내부 슬롯 [[Environment]]에 저장한다.
const x = 1;
function funcOne() {
const x = 5;
// 함수의 호출 위치와 상위 스코프는 아무런 관계가 없다.
funcTwo();
};
function funcTwo() {
// 함수 funcTwo는 자신의 상위 스코프를 내부슬롯 [[Environment]]에 저장하여 기억한다.
console.log(x);
};
funcOne(); // 1
funcTwo(); // 1
외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다.
const x = 1;
function outer() {
const x = 5;
// 중첩 함수 inner은 클로저
const inner = function() {
console.log(x);
};
return inner;
};
// 함수 outer을 호출하면 중첩 함수 inner을 반환하고, outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거된다.
const innerFunc = outer();
innerFunc(); // 5
위의 예제에서, 함수 outer의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만, outer의 렉시컬 환경까지 소멸하는 것은 아니다. 함수 outer의 렉시컬 환경은 함수 inner의 [[Environment]] 내부 슬롯에 의해 참조되고 있고 inner은 전역 변수 innerFunc에 의해 참조되고 있다.
📢 클로저에 의해 참조되는 상위 스코프의 변수(예제에서 outer 함수의 x)를 자유 변수
라고 부른다. 클로저란 자유 변수에 묶여있는 함수라고도 할 수 있다.
상태(state)를 안전하게 은닉, 특정 함수에게만 상태 변경 허용
함수가 호출될 때마다 횟수를 누적하여 출력하는 카운터를 만들어보자.
let num = 0;
const increase = function() {
return num++;
};
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
카운트 상태(num)는 increase 함수가 호출되기 전까지 변경되지 않아야 하고, 카운트 상태(num)는 increase 함수만이 변경할 수 있어야 한다. 하지만 위의 예제에서 카운트 상태가 전역에서 관리되고 있으므로 누구나 접근할 수 있고 변경할 수 있는 위험이 있다.
// 카운트 상태 변경 함수
const increase = (function() {
let num = 0;
// 클로저
return function() {
return num++
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
위의 예제에서, 즉시 실행 함수가 반환한 클로저는 카운트 상태를 유지하기 위한 자유 변수 num을 어디서 호출하든지 참조하고 변경할 수 있다. 또한 num 변수는 은닉된 private 변수이므로 의도되지 않은 변경을 걱정하지 않아도 된다.
참고
모던 자바스크립트 Deep Dive (도서)