클로저는 JS만의 기술이 아니다.
클로저는 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성이다.
일급 객체 함수의 개념을 이용해서 스코프에 묶인 변수를 바인딩 하기 위한 일종의 기술이다.
다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 보통 함수에 매개변수로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다.
어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상
즉 내부함수에서 외부함수의 지역변수에 접근 할 수 있는 것
var outer = function(){
var a = 1;
var inner = function(){
console.log(++a)
}
inner();
}
outer();
outer함수에서 변수 a를 선언했고, outer의 내부함수인 inner함수에서 a의 값을 1 증가시킨 다음 출력한다. inner 함수 내부에 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못하므로 outerEnvironmentReference에 지정된 상위 컨텍스트인 outer의 L.E에 접근해서 다시 a 를 찾는다. 2가 출력이 되고, outer함수의 실행 컨텍스트가 종료되면 L.E에 저장된 식별자들에 대한 참조를 지운다. 그러면 각 주소에 서장돼 있던 값들은 자신을 참조하는 변수가 없기 때문에 가비지 컬렉터의 수집 대상이 된다.
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2); //2
이 코드에서도 inner 내부에서 외부변수인 a를 사용했다. 그러나 inner함수를 실행한 결과를 반환하고 있으므로 결과적으로 outer함수의 실행컨텍스트가 종료된 시점에는 a변수를 참조하는 대상이 없어진다. 따라서 inner 변수의 값들은 언젠간 G.C에 의해 소멸한다. 위 두 예는 outer 함수의 실행 컨텍스트가 종료되기 이전에 inner함수의 실행 컨텍스트가 종료돼 있고, 이후 별도로 inner 함수를 호출할 수 없다는 공통점이 있다.
그렇다면 outer실행 컨텍스트가 종료된 후에도 inner 함수를 호출할 수 있게 만든다면?
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2()); //2
console.log(outer2()); //3
여기서는 inner함수의 반환값이 아닌 inner함수 자체를 반환했다. 그렇다면 outer 함수의 실행컨텍스트가 종료될 때 outer2변수는 outer의 실행결과인 inner 함수를 참조할 것이다. 그 다음 outer2를 호출하면 반환된 함수은 inner가 실행될 것이다.
inner 함수의 실행 컨텍스트의 environmentRecord에는 수집할 정보가 없다. outer-environmentRecord에는 inner 함수가 선언된 위치의 L.E가 참조복사된다. inner 함수는 outer함수 내무에서 선언됐으므로, outer 함수의 L.E가 담길 것이다. 이제 스코프체이닝에 따라 선언함 변수 a에 접근해서 1만큼 증가시킨후 그 값인 2를 반환하고, inner 함수의 실행 컨텍스트가 종료된다. 그 다음 다시 outer2를 호출하면 같은 방식으로 a의 값을 2에서 3으로 증가시킨후 반환한다.
하지만 inner함수의 실행 시점에는 outer함수가 이미 종료된 상태인데 어떻게 outer함수의 L.E에 접그느 할 수 있는 걸까?
이는 가비지컬렉터(GC)의 동작 방식 때문이다.
GC는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집대상에 포함시키지 않는다. outer함수는 실행 종료 시점에 inner 함수를 반환한다. 외부함수인 outer의 살행이 종료되도 내부 함수인 inner함수는 언젠간 outer2를 실행함으로써 호출된 가능성이 열린 것. 언젠가 inner함수의 실행 컨텍스트가 활성화 되면 outerEnvironmentRecord가 outer 함수의 L.E를 필요로 할 것이므로 수집대상에서 제외된다. 그 덕에 inner함수가 접근할 수 있는 것이다.
지금까지의 내용으로 다시 클로저를 정의하자면 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는현상이라고 할 수 있다.
그러나 '외부로 전달'이 곧 return을 의미하는 것은 아니다.
(function () {
var a = 0;
var intervalId = null;
var inner = function () {
if (++a >= 10) {
clearInterval(intervalId);
}
console.log(a);
};
intervalId = setInterval(inner, 1000);
})();
위의 코드는 별도의 외부객체인 window의 메소드(setInterval)에 전달할 콜백 함수 내부에서 지역변수를 참조한다.
(function () {
var count = 0;
var button = document.createElement('button')
button.innerText = 'click'
button.addEventListener('click', function(){
console.log(++count, '번 클릭')
})
document.body.appendChild(button)
})();
별도의 외부객체인 DOM의 메소드에 등록할 handler 함수 내부에서 지역변수를 참조한다.
위 두 상황 보두 지역변수를 참조하는 내부함수를 외부에 전달했기 때문에 클로저라고 할 수 있따.