* 이 글은 코어자바스크립트 책을 참고하여 기록하였습니다.
어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상.
- 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 "외부로 전달"할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상.
- "외부로 전달"에 의미가 오직 return 만을 의미하는 것은 아님.
var outer = function (){
var a = 1;
var inner = function (){
console.log(++a); // 2
}
inner();
};
outer();
* 예시를 통해 알아야 할 것들 🤔
-
outer 함수에서 변수 a를 선언.
-
outer의 내부 함수 inner 함수에서 a의 값을 1만큼 증가시킨 다음 출력.
-
inner 함수 내부에서 a를 선언하지 않았기에, enviromentRecord 에서는 값을 찾지 못함.
-
outerEnvironmentReference 에 지정된 상위 컨텍스트 outer의 LexicalEnvironment에 접근해서 a를 찾고 2를 출력.
-
outer 함수의 실행컨텍스트가 종료 될 때, LexicalEnvironment에 저장된 식별자(a, inner)에 대한 참조를 지움.
-
참조하는 변수가 없는 값들은 가비지 컬랙터의 수집대상이 됨.
var outer = function (){
var a = 1;
var inner = function (){
return ++a;
}
return inner();
};
var outer2 = outer();
console.log(outer2); // 2
* 예시를 통해 알아야 할 것들 🤔
-
outer 함수에서 변수 a를 선언.
-
outer의 내부 함수 inner 함수에서 a의 값을 1만큼 증가시킨 다음 결과 값을 리턴.
-
outer 함수에서 inner 함수를 실행한 결과를 리턴.
-
outer 함수의 실행컨텍스트가 종료 될 때, LexicalEnvironment에 저장된 식별자(a, inner)에 대한 참조를 지움.
-
참조하는 변수가 없는 값들은 가비지 컬랙터의 수집대상이 됨.
- 결과적으로 예시 1번과 2번은 함수의 실행 컨텍스트가 종료될 때 LexicalEnvironment에 저장된 식별자에 대한 참조를 지우므로, 가비지 컬렉터의 수집대상이 됨.
var outer = function (){
var a = 1;
var inner = function (){
return ++a;
}
return inner;
};
var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
* 예시를 통해 알아야 할 것들 🤔
-
outer 함수에서 변수 a를 선언.
-
outer의 내부 함수 inner 함수에서 a의 값을 1만큼 증가시킨 다음 결과 값을 리턴.
-
outer 함수에서 inner 함수 자제를 리턴.
-
outer 함수의 실행컨텍스트가 종료 될 때, outer2 변수는 outer의 실행 결과인 inner 함수를 참조.
-
outer2를 호출하면 반환된 함수인 inner 함수가 실행.
-
inner 함수의 실행 컨텍스트의 enviromentRecord 에는 수집할 대상이 존재하지 않음.
-
이때 outerEnvironmentReference 에는 inner 함수가 선언된 위치의 LexicalEnvironment가 참조 복사됨. (중요)
-
inner 함수는 outer 함수 내부에 선언되었으므로, outer 함수의 LexicalEnvironment가 담김.
-
스코프 체이닝에 의해 outer에 선언한 변수 a에 접근, 1을 증가 2를 반환, inner 함수의 실행 컨텍스트 종료.
-
다시 outer 함수 호출, 선언한 변수 a에 접근, 값 2에서 1 증가 3를 반환, inner 함수의 실행 컨텍스트 종료.
- inner 함수의 실행 시점에서 outer 함수는 이미 종료되었음에도 불구하고 outer 함수의 LexicalEnvironment의 접근할 수 있는 이유는 가비지 컬렉터의 동작 방식 때문.(중요)
- 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 수집대상에 포함시키지 않음.
- outer 함수가 종료, 실행 컨텍스트가 종료되었음에도 내부 함수인 inner 함수가 반환되어 outer2 변수에 담기면서, 함수를 호출 할 가능성이 존재하므로, 가비지 컬렉터의 대상이 되지 않아 a라는 변수에 접근할 수 있게 됨.
- 결론적으로, 지역변수를 참조하는 내부함수가 외부로 전달 된 경우가 가비지 컬렉터의 수집 대상에서 제외된다고 할 수 있음.
- 클로저는 "외부 함수의 LexicalEnvironment가 가비지 컬렉팅되지 않는 현상" 자체.
# 1 setInterval / setTimeout 함수
(function (){
var a = 0;
var intervalId = null;
var inner = function (){
if(++a >= 10){
clearInterval(intervalId);
}
console.log(a); // (1초에 한번 씩) 1 2 3 4 5 6 7 8 9 10
};
intervalId = setInterval(inner, 1000);
})();
#2
(function (){
var count = 0;
var button = document.createElement('button');
button.innerText = 'click';
button.addEventListener('click', function (){
console.log(++count, 'times clicked');
});
document.body.appendChild(button);
})();
* 예시를 통해 알아야 할 것들 🤔
-
#1은 외부객체인 window의 메서드 setInterval에 전달할 콜백 함수 내부에서 지역변수를 참조.
-
#2는 외부객체인 DOM의 메서드 addEventListenerd에 등록 할 콜백 함수 내부에서 지역변수를 참조.
- 결과적으로 #1, #2번 모두 지역변수를 참조하는 내부함수를 외부에 전달, 그렇기에 클로저.
읽어 주셔서 감사합니다.
<참고자료>
[책] #코어자바스트립트 - 정재남 지음 -
<클로저> 클로저의 의미 및 원리 이해 end