클로저는 자바스크립트 고유의 개념이 아니므로 클로저의 정의가 ECMAScript 사양에 등장하지 않는다.
MDN에서는 클로저에 대해 다음과 같이 정의하고 있다.
"클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다."
외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다.
이러한 중첩 함수를 클로저라고 부른다.
const x = 1;
function outer() {
const x = 10;
const inner = function() { console.log(x); };
return inner;
}
const innerFunc = outer();
innerFunc();
이때 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되어도 outer 함수의 렉시컬 환경은 유지된다.
outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고 inner 함수는 전역 변수 innerFunc에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 되지 않기 때문이다.
이처럼 외부 함수보다 더 오래 생존한 중첩 함수는 외부 함수의 생존 여부와 상관없이 자신이 정의된 위치에 의해 결정된 상위 스코프를 기억한다.
자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저지만, 일반적으로 모든 함수를 클로저라고 하지는 않는다.
위와 같은 경우는 클로저가 아니다.
클로저는 중첩 함수가 상위 스코프의 식별자를 참조하며 외부 함수보다 더 오래 유지되는 경우에 한정한다.
➕➕➕
- 자유 변수 : 클로저에 의해 참조되는 상위 스코프의 변수
- 모던 자바스크립트 엔진 => 상위 스코프의 식별자 중 필요한 것만 기억 (최적화 Good) => 메모리 낭비X
let num = 0;
const increase = function() {
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
위 코드는 오류를 발생시킬 가능성을 내포하고 있다.
카운트 상태는 전역 변수 num을 통해 관리되므로 언제든지 누구나 접근하여 의도치 않은 변경을 할 수 있다.
const increase = function() {
let num = 0;
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1
num을 increase 함수의 지역 변수로 변경하여 이제 num은 increase 함수만이 변경할 수 있다.
그러나 increase 함수가 호출될 때마다 지역 변수 num은 다시 선언되고 0으로 초기화되므로 출력 결과는 언제나 1이다.
즉, 상태가 변경되기 이전의 상태를 유지하지 못한다.
이러한 문제는 클로저를 사용하면 해결된다.
const increase = (function() {
let num = 0;
return function() {
return ++num;
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
자바스크립트는 정보 은닉을 완전하게 지원하지 않는다.
인스턴스 메소드를 사용하면 자유 변수를 통해 private를 흉내낼 수 있지만, 프로토타입 메소드를 사용하면 이마저도 불가능해진다.
ES6DML Symbol이나 WeakMap을 이용해도 근본적인 해결책이 되지는 않는다.
=> 현재 클래스에 private 필드를 정의할 수 있는 새로운 표준 사양이 제안되어 있는 상태