❓클로저란
일반적으로 클로저는 함수와 해당 함수가 정의되어 있는 렉시컬 환경과의 조합을 의미한다. 하지만 클로저의 본질에 부합하기 위해서는 생명 주기가 종료된 외부 함수의 변수를 참조할 수 있어야 한다. 클로저는 스코프, 실행 컨텍스트와 밀접한 관련이 있기 때문에 이전에 정리해두었던 스코프, 실행 컨텍스트 포스팅을 먼저 참고했으면 한다.
1️⃣ 클로저와 렉시컬 환경
const name = "Lee";
function outer() {
const name = "Kim";
function inner() {
console.log(`Hi! ${name}`);
}
return inner;
}
const closure = outer();
closure(); // ?
위의 예시를 통해 클로저의 동작 방식에 대해서 설명해보고자 한다. 해당 코드에서 outer 함수를 실행했을 때의 실행 컨텍스트를 만들면 아래와 같은 그림으로 나타낼 수 있다.
저번 포스팅에서 함수 선언문으로 정의된 함수는 코드가 평가될 때 함수 객체가 생성되고, 자바스크립트 엔진은 함수 객체를 생성할 때 실행중인 실행 컨텍스트의 렉시컬 환경을 함수 객체의 내부 슬롯인 [[Environment]]에 저장한다고 했다. 따라서 해당 슬롯에 저장된 값은 해당 함수의 상위 스코프 값이다.
이 때 함수가 호출된 후 코드 실행 과정을 거쳐 outer 함수가 종료되면 inner 함수를 반환하면서 outer 함수가 실행 컨텍스트에서 pop 된다. 그렇다면 지금 실행 컨텍스트에는 전역 실행 컨텍스트와 전역 실행 컨텍스트의 렉시컬 환경만 남게되는 것일까?
outer 함수의 렉시컬 환경은 중첩함수인 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고, 해당 inner 함수는 전역 변수인 closure에 의해 참조되고 있기 때문에 가비지 컬렉션의 대상이 되지않으며 결과적으로 outer 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 제거된다해도 outer 함수의 렉시컬 환경은 남아있게 된다.
이후에 closure() 함수를 호출하면 다음과 같이 실행 컨텍스트가 만들어 진다.
중첩함수인 inner 함수는 외부함수인 outer 함수보다 더 오래 살아있고, 외부함수의 실행 컨텍스트의 존재 여부에 상관없이 상위 스코프를 기억하고 있기 때문에 상위 스코프의 식별자를 참조하는 것과, 식별자의 값을 변경하는 것 모두 가능하다. 이렇게 클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수라고 한다.
결과적으로는 "Hi! Lee"가 출력된다.
2️⃣ 클로저의 활용
클로저는 대표적으로 상태가 의도치 않게 변경되는 것을 막기위한 정보 은닉을 위해서 사용된다. 다음 예제를 살펴보자.
const counter = (function () {
let num = 0;
return function(predicate) {
num = predicate(num);
return num;
}
}())
function increase(state) {
return ++state;
}
function decrease(state) {
return --state;
}
console.log(counter(increase)); // 1
console.log(counter(decrease)); // 0
위의 예제는 즉시 실행 함수를 통해 해당 함수의 렉시컬 환경을 만들고 클로저를 반환한다. 인수로 전달 받은 보조 함수를 통해 해당 함수의 자유 변수를 변경한다. 따라서 외부에서 접근할 수 없는 은닉된 변수를 만들고 특정 함수를 통해서만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지할 수 있다.