클로저를 모르고 있다고 생각했는데, 이미 클로저를 은연중에 사용하고 있었다는 사실에 신기하게 느껴졌다!
클로저 ( closure ) 는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
내부함수가 정의될 떄 외부함수의 환경을 기억하고있는 내부함수를 말한다. 즉, 자신이 생성될 때의 환경을 기억하는 함수
함수 정의가 평가되는시점 ( 실행 x ) 에 함수가 상위스코프의 렉시컬환경을 참조를 하게 되는 것이다.
클로저를 정확하게 이해하기 위해서는 함수의 실행컨텍스트를 확실히 이해해야한다.
const makeCounter = () => {
let count = 0; // 독립변수, 자유변수
return () => {
return count += 1;
}
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
익명함수가 선언될 때 익명함수의 내부슬롯에 makeCounter의 렉시컬환경을 참조한다.
const counter = makeCounter(); 에서 makeCounter 함수가 호출될 떄 makeCounter 함수의 컨텍스트가 생성된다.
makeCounter함수의 컨텍스트안에 변수 count가 기록되고 익명함수 () => {} 가 기록, 정의된다.
(익명)함수가 정의될 때 (익명)함수는 자신의 내부슬롯 [[Environment]] 에 상위스코프의 렉시컬환경이 담긴다.
(익명)함수가 정의된 상위스코프 makeCounter()의 렉시컬환경을 (익명)함수의 내부슬롯에 담아놨기때문에, 계속 참조를 안끊기고 하므로 가비지컬렉션의 대상이 되지않아 참조가 계속 살아있다.
이렇게 함수객체는 정의되었을 때 자신의 내부슬롯에 그 상위 스코프의 렉시컬환경참조가 담기는것이다.
여기서 let count = 0; 은 독립변수 또는 자유변수 라고하는데,
이것이 바로 클로저를 쓰는 이유가 된다.
변수를 외부에서 접근할수없게 하기위해서, 변수를 은닉하고, 독립적으로 쓰고싶을 때 클로저패턴을 쓴다. ( 모듈화를 위함이라고도 한다. )
전역변수가 많으면 어디에서든 접근이 가능하므로 최대한 전역변수를 줄여서 코딩을 해야한다.
하지만 프로그램을 구현하다보면 이 함수 하나에서만 사용하는데 전역변수가 필요한 순간이 오는데, 이 때 클로저를 사용하면 된다.
상태 변경이나 가변 데이터를 피하고 오류를 피하는 안정성을 증가 시킬수 있다.
스코프가 종료 된 후에도 스코프 밖에서 언제든지 호출 될 수 있도록 메모리에 계속해서 저장하고 있기 때문에 메모리 사용량이 늘어난다.
클로저 해제는 null
을 대입한다고 한다.