- 클로저는 함수와 함수가 선언된 어휘적 환경(Lexical environment)의 조합이다. (MDN)
- 어떤 함수 A에서 선언한 변수 a를 참조하는 내부 함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상 (코어 자바스크립트)
Lexical Environment는 식별자와 참조 혹은 값을 기록하는 LexicalEnviormentRecord와 외부 Lexical Enviornment를 참조하는 포인터인 OuterEnvironmentReference를 갖고 있다.
// 실제로 이렇게 동작하진 않지만 개념적으로 이러하다
function foo() {
const a = 1;
const b = 2;
const c = 3;
function bar() {}
// 2. 실행 컨텍스트 동작
// ...
}
foo(); // 1. 함수 호출
// 실행 컨텍스트의 렉시컬 환경
{
environmentRecord: {
a: 1,
b: 2,
c: 3,
bar: <Function>
},
outer: foo.[[Environment]]
}
function doSomthing(x) {
const x = 10;
function sum(y) {
return x + y;
};
return sum;
}
const add = doSomthing(2);
console.log(add(7));
실행 컨텍스트 과정을 쓰자면
+) ReferenceError가 일어나는 과정 또한 record => outer => record => outer를 반복해 실행 과정 중 없는 변수를 찾다가 마지막 outer인 전역(Global) 렉시컬 환경의 outer는 없으므로 ReferenceError가 나오게 된다.
function Counter() {
// 카운트를 유지하기 위한 자유 변수
var counter = 0;
// 클로저
this.increase = function () {
return ++counter;
};
// 클로저
this.decrease = function () {
return --counter;
};
}
const counter = new Counter();
console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0
이처럼 private이 존재하지 않는 바닐라 js에서 클로저 함수를 만들어 사용해 private 키워드를 흉내낼 수 있다. (다만 ES2019에서 해쉬 # prefix를 추가해 private class 필드를 선언할 수 있다.MDN - Private class fields)
// 실제로 이렇게 동작하진 않지만 개념적으로 이러하다
import React from "react";
let state = [];
let setters = [];
let cursor = 0;
let firstrun = true;
const createSetter = (cursor) => {
return (newValue) => {
state[cursor] = newValue;
};
};
const customUseState = (initialValue) => {
if (firstrun) {
state.push(initialValue);
setters.push(createSetter(cursor));
firstrun = false;
}
const resState = state[cursor];
const resSetter = setters[cursor];
cursor++;
return [resState, resSetter];
};
export default function App() {
cursor = 0;
const [counter, setCounter] = customUseState(0);
return (
<>
<div>{counter}</div>
<button onClick={() => setCounter(counter + 1)}>+</button>
<button onClick={() => setCounter(counter - 1)}>-</button>
</>
);
}
이처럼 React의 useState를 통해 생성한 상태를 접근하고 유지하기 위해 useState 바깥에 state를 저장하는데
이 state들은 배열 형식으로 저장되며 상태가 업데이트 되었을 때 컴포넌트 바깥의 변수들이기 때문에 업데이트 한 후에도 이 변수들에 접근이 가능하다.
즉 counter와 setCounter는 컴포넌트 바깥의 private한 state 배열을 참조 및 수정할 수 있는 클로저 함수인 셈이다.
참고자료