closure
)클로저closure
는
ECMAScript
사양에 등장하지는 않지만, MDN
에서는 클로저를 이렇게 정의하고 있다.자바스크립트 엔진은
외부 렉시컬 환경에 대한 참조
를 통해 상위 렉시컬 환경과 연결된다. 함수의 상위 스코프를 결정한다
는 것은 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다
는 것과 같다.외부 렉시컬 환경에 대한 참조
에 저장할 참조값이 바로 상위 렉시컬 환경에 대한 참조이며, 이 것이 상위 스코프이기 때문이다외부 렉시컬 환경에 대한 참조
에 저장할 참조값, 즉 상위 스코프에 대한 참조는[[Environment]]
렉시컬 스코프 개념이 가능하려면
[[Environment]]
에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.[[Environment]]
에 저장된 현재 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 바로 상위 스코프다.외부 렉시컬 환경에 대한 참조
에 저장될 참조값이다.[[Environment]]
에 저장한 렉시컬 환경의 참조, 즉 상위 스코프를 자신이 존재하는 한 기억한다.const x = 1;
// ①
function outer() {
const x = 10;
const inner = function () { console.log(x); }; // ②
return inner;
}
// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10
outer
함수를 호출하면
outer
함수는 inner
함수를 반환하고 생명 주기를 마감하는데 outer
함수의 지역 변수 x
또한 생명 주기를 마감한다.outer
함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer
함수의 렉시컬 환경까지 소멸되는 것은 아니다.outer
함수의 렉시컬 환경은 inner
함수의 [[Environment]]
내부 슬롯에 의해 참조되고 있고 inner
함수는 전역변수 innerFunc
에 의해 참조되고 있으므로 outer
함수의 지역변수 x
값인 10이 동작하는데 클로저는
외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저(closure) 라고 부른다.
자바스크립트의 모든 함수는 자신의 상위 스코프를 기억한다.
자바스크립트의 모든 함수는
클로저는
state
)를 안전하게 변경하고 유지하기 위해 사용한다. information hiding
)하고 // 카운트 상태 변수
let num = 0;
// 카운트 상태 변경 함수
const increase = function () {
// 카운트 상태를 1만큼 증가 시킨다.
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
위의 코드는 다음과 같은 이유로 오류를 발생시킬 가능성을 내포하고 있는 좋지 않은 코드이다.
increase
함수가 호출되기 전까지 변경되지 않고 유지되어야 한다.increase
함수만이 변경 할 수 있어야한다.하지만 카운트 상태는 전역 변수를 통해 관리 되고 있기 때문에 의도치 않게 상태가 변경될 수 있다.
increase
함수만이 num
변수를 참조하고 변경할 수 있도록 num
을 increase
함수의 지역변수로 바꾸고 클로저를 사용해 주는것이 바람직하다.// 카운트 상태 변경 함수
const increase = (function () {
// 카운트 상태 변수
let num = 0;
// 클로저
return function () {
// 카운트 상태를 1만큼 증가 시킨다.
return ++num;
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
다음은 함수형 프로그래밍에서 클로저를 활용하는 간단한 예제다.
// 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
const counter = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 함수를 인수로 전달받는 클로저를 반환
return function (aux) {
// 인수로 전달 받은 보조 함수에 상태 변경을 위임한다.
counter = aux(counter);
return counter;
};
}());
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 보조 함수를 전달하여 호출
console.log(counter(increase)); // 1
console.log(counter(increase)); // 2
// 자유 변수를 공유한다.
console.log(counter(decrease)); // 1
console.log(counter(decrease)); // 0
makerCounter
함수를 호출해 함수를 반환할 때
반환된 함수는 자신만의 독립된 렉시컬 환경을 갖기 때문에 주의해야 한다.
캡슐화는
public
, private
, protected
같은 접근 제한자를 선언하여 공개 볌위를 한정할 수 있다.자바스크립트는 정보 은닉을 완전하게 지원하지 않는다. 인스턴스 메서드를 사용한다면 자유 변수를 통해 private을 흉내 낼 수는 있지만 프로토타입 메서드를 사용하면 이마저도 불가능해진다
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () { return i; }; // ①
}
for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // ②
}
for
문의 코드블록 내에서
var
키워드로 선언한 변수 i
는 블록 레벨 스코프가 아닌 함수 레벨 스코프를 갖기 때문에 전역변수이다. i
를 참조하여 i
의 값 3을 출력한다.const funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () { return i; };
}
for (let i = 0; i < funcs.length; i++) {
console.log(funcs[i]()); // 0 1 2
}
위 예제는 자바스크립트의 함수 레벨 스코프 특성으로 인해
for
문의 변수 선언문에서 var
키워드로 선언한 변수가 전역 변수가 되기 때문에 발생하는 현상이다. let
키워드를 사용하면 이 같은 번거로움이 깔끔하게 해결된다.
const
나let
키워드를 사용하는 반복문(for문, for ... in 문, for ... of 문, while 문 등)은 코드 블록을 반복 실행할 때마다 새로운 렉시컬 환경을 생성하여 반복할 당시의 마지막 상태를 마치 스냅샷을 찍는 것 처럼 저장한다.