클로저란?
MDN에서는 "A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)."라고 소개한다.
"클로저는 함수의 주변 상태(어휘 환경)에 대한 참조와 함께 묶인 함수의 조합이다."어떤 함수 A 내부에서 선언한 변수를 참조하는 내부 함수 B를 외부로 전달할 경우, A의 실행 컨텍스트가 종료된 후에도 변수가 사라지지 않는 현상
<< 코어 자바스크립트 >>
var outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
return inner;
};
var outer2 = outer();
outer2(); // 2
outer2(); // 3
outer 함수의 반환값으로 inner 함수의 실행 결과가 아닌 함수 자체를 반환한다. 그러면 outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 inner 함수를 참조하게 되고, outer2를 호출하면 inner 함수가 실행된다.
outer2 함수를 실행했을 때, 왜 같은 값을 반환하지 않고 a의 값이 늘어나는 것일까?
outer2 함수를 실행했을 때 흐름을 보면, inner 함수의 environmentRecord에는 a 변수가 없기에 스코프 체이닝에 따라 outer에서 선언한 변수 a에 접근한다. outer 함수는 이미 종료되었지만 inner 함수가 a를 참조하고 있기 때문에 가비지 컬렉터의 수집 대상이 되지 않아 변수 a는 존재할 수 있다.
즉, 어떤 함수 내부에서 선언한 변수가 외부에서 호출될 가능성(참조)이 있다면 가비지 컬렉터의 수집 대상이 되지 않고 함수가 종료된 이후에도 남아 있는 것이다.
참고
실행 컨텍스트
https://velog.io/@ss_kim/execution-context
클로저를 사용하여 어떤 값의 참조 카운트가 1 이상이라면 메모리에 남아 있게 된다. 그렇기에 필요성이 사라진 시점에서 메모리를 소모하지 않도록 참조 카운트를 0으로 만들어 메모리를 관리해주어야 한다.
var outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
return inner;
};
var outer2 = outer();
outer2();
outer2();
outer2 = null; // inner 함수 참조를 끊음
클로저를 어떤 상황에서 사용할 수 있을까?
outer 함수는 외부로부터 격리된 공간이다. 외부에서 outer 함수를 실행하는 것은 가능하지만 outer 함수의 내부에 접근하는 것은 불가능하다. 위 예시에서 외부에서 변수 a의 값을 재할당하는 것을 불가능하다.
클로저를 각각의 변수에 할당하면 함수를 독립적인 부품 형태로 분리할 수 있다.
var outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
return inner;
};
var outer2 = outer();
var outer3 = outer();
outer2(); // 2
outer2(); // 3
outer3(); // 2
outer3(); // 3
이를 활용해 데이터와 메서드를 묶을 수 있다.
const counter = () => {
let value = 0;
return {
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
}
const counter1 = counter();
counter1.increase(); // 1
counter1.increase(); // 2
counter1.decrease(); // 1
const counter2 = counter();
counter2.decrease(); // -1
counter2.decrease(); // -2
counter2.increase(); // -1
함수형 컴포넌트는 렌더링될 때마다 함수를 호출한다. 그렇기에 리렌더링될 때 이전 상태를 기억하고 있어야 하는데, useState의 개념을 클로저를 이용해 비슷하게 표현하면 아래와 같다.
let state
function useState(initialValue){
if(state === undefined){
state = initialValue
}
const setState = (newValue) => {
state = newValue
}
return [state, setState]
}
최초 렌더링 시 initialValue를 state에 할당해서 반환하고, 상태 업데이트 함수 setState를 호출하면 newValue를 state에 재할당 후 반환한다.
참고
https://velog.io/@radin/js-closure
https://yeoulcoding.tistory.com/149#recentEntries