클로저
function makeClosure() { // 외부함수
const name = "luasenvy"; // 외부함수 멤버
return function someFn() { // 내부함수
return name; // 외부함수 멤버 참조
}
}
const fn = makeClosure();
fn(); // "luasenvy"
클로저는 선언적인어휘적인 실행환경을 일컫는다. 말이 좀 어려운데 위 예시를 보면 makeClosure()
함수는 someFn()
함수를 반환한다. someFn 함수는 makeClosure 함수의 내부에서 선언된 함수로 외부함수에서 선언된 name을 접근할 수 있고 이 값을 반환하고 있다. 이 연결을 잘 생각해보면 makeClosure -> someFn -> makeClosure.name 이런 식으로 참조하고 있다.
- makeClosure() 실행
- someFn 함수 반환 -> fn 변수에 할당
- fn() 실행
name이라고 하는 외부함수 멤버는 외부함수가 실행중일 때만 유효하다. makeClosure 밖에서 존재하지 않는 변수임은 너무나도 당연해보인다. 또한, someFn()을 실행할 때에도 그 어디에도 name 변수를 초기화 하는 코드는 없다. 직관적으로 보았을 때 someFn()을 실행하고 name을 참조하는 순간에 Uncaught ReferenceError: name is not defined 오류가 발생할 것으로 예상할 수 있다.
그러나 자바스크립트는 someFn이 반환될 때 선언된어휘적 위치에 맞는 환경컨텍스트인 클로저를 구성하여 이 참조들이 올바르게 작동할 수 있도록 유지시킨다. 이러한 작동은 여러가지를 가능하게 해주지만 유지보수가 복잡해지고 예상하지 못했던 동작을 만날 수도 있다.
응용1. 함수 팩토리
const makeFn = (width) => {
return (el) => {
el.style.width = width
};
}
const changeWidth100 = makeFn(100);
const changeWidth200 = makeFn(200);
div1.addEventListener(e => changeWidth100(e.target));
div2.addEventListener(e => changeWidth200(e.target));
이 클로저를 활용하면 함수 팩토리처럼 사용할 수 있다.
응용2. 비공개 멤버 구현
const human = (() => {
let age = 0;
const moreThanFive = () => age > 5;
return {
isOverFive() {
return moreThanFive();
},
setAge(_age) {
age = _age;
},
getAge() {
return age;
},
};
})();
console.info(human.getAge());
과거 자바스크립트에서는 private 멤버를 만들 수 있는 문법이 제공되지 않았기 때문에 위처럼 클로저를 응용하여 비공개 멤버를 흉내낼 수 있도록 하였다. moreThanFive()
, age 변수는 클로저가 생성되면서 참조를 잃지 않고 유지되지만 외부에서 접근하려면 isOverFive()
, getAge()
, setAge()
와 같은 명시적으로 공개한 메서드를 사용해야만 한다.