외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료된 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.
자바스크립트의 모든 함수는 자신의 상위 스코프를 기억한다. 따라서 함수를 어디서 호출하든 상관없이 함수는 언제나 자신이 기억하는 상위 스코프의 식별자(변수)를 참조할 수 있으며, 식별자(변수)에 바인딩된 값을 변경할 수도 있다.
const x = 1;
function outer() {
const x = 10;
const inner = function () { console.log(x); };
return inner;
} // >> 지역 변수 x의 생명 주기 종료 시점
// outer 함수 호출로 반환된 inner 함수를 할당한다.
const innerFunc = outer();
innerFunc(); // 10, 반환된 함수 inner에는 변수 x가 존재하지 않지만 10을 출력한다.
위의 예제에서 중첩 함수 inner는 외부 함수 outer보다 더 오래 생존했다. 이때 외부 함수보다 더 오래 생존한 중첩함수는 외부 함수의 생존 여부와 상관없이 자신이 정의된 위치에 의해 결정된 상위 스코프를 기억한다.
이처럼 중첩 함수 inner의 내부에서는 상위 스코프를 참조할 수 있으므로 상위 스코프의 식별자를 참조할 수 있고 식별자의 값을 변경할 수도 있다. 예제에서도 inner의 내부에 변수 x가 존재하지 않지만 상위 스코프인 outer 함수의 식별자를 참조하여 10을 출력하였다.
inner와 같은 중첩 함수를 클로저라고 한다.
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
함수가 호출될 때마다 호출된 횟수를 누적하여 출력하는 코드를 작성해보자. 예제에서 호출된 횟수(num 변수)가 안전하게 변경하고 유지해야할 상태다.
let num = 0; // 카운트 상태 변수
const increase = function () {
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
위 예제에서 개선해야할 점은 다음과 같다.
위의 내용을 고려하여 전역 변수 num을 increase 함수의 지역 변수로 바꾸어 의도치 않은 상태 변경을 방지해보자.
const increase = function () {
let num = 0; // 카운트 상태 변수
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1
전역변수를 지역변수로 변경하여 상태 변경을 방지했지만, 변수 num이 increase 함수가 호출될 때마다 0으로 초기화되는 문제가 있다.
클로저를 사용하여 문제를 해결할 수 있다.
const increase = function () {
let num = 0; // 카운트 상태 변수
// 클로저
return function(){
return ++num;
};
};
const count = increase();
console.log(count()); // 1
console.log(count()); // 2
console.log(count()); // 3
increase 함수가 반환한 함수가 count 변수에 할당되고, 할당된 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프인 increase 함수를 기억한다. 따라서 increase 함수의 지역 변수 num을 언제 어디서 호출하든지 참조하고 변경할 수 있다.
따라서 클로저가 할당된 count 함수가 계속 호출되면서 num 값이 변경된다.
위의 코드에 즉시실행함수를 적용하여 코드를 더 발전시켜보자. increase 함수가 반환한 클로저를 바로 호출하고 있으므로 굳이 변수에 담아줄 필요없이 즉시실행함수를 사용할 수 있다.
const increase = function () {
let num = 0; // 카운트 상태 변수
// 클로저
return function(){
return ++num;
};
}();
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
즉시실행함수는 호출된 이후 소멸되지만 즉시실행함수가 반환한 클로저는 increase 변수에 할당되어 호출된다. 이때 클로저는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시실행함수를 기억하고 있다. 따라서 변수 num을 참조하고 변경할 수 있다.
또한 즉시실행함수는 한 번만 실행되므로 increase가 호출될 때마다 num 변수가 초기화되지 않는다. 정확히 즉시실행함수가 실행된 후 반환된 클로저가 계속 실행되면서 num 값을 반환한다.
그리고 num 변수는 외부에서 직접 접근할 수 없는 은닉된 private 변수가 된다. 따라서 클로저를 사용하여 안정적인 프로그래밍이 가능하다.
이처럼 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.
조오아요