클로저의 다양한 정의
Lexical environment와 클로저
번역: 클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따르 현상
근데 선언될 당시가 언제???
실행 컨텍스트 생성 시,
1. VariableEnvironment에 정보를 먼저 담는다
2. 그대로 복사해서 LexicalEnvironment를 만든다
3. 이후 주로 LexicalEnvironment를 활용한다
1) environmentRecord에 현재 컨택스트와 관련된 코드의 식별자 정보들이 저장됨
2) outerEnvironmentReference는 변수의 유효범위인 스코프를 결정함
★ 스코프: 식별자에 대한 유효범위
천천히 이해해보면,
MDN의 정의를 다시 곱씹어 보면,
내부함수에서 외부 변수를 참조하지 않는 경우라면 combination의 상황이 아닌 것.
즉, 내부함수에서 외부변수를 참조하는 경우에 한해서만 combination 조건이 충족함 - 이때 비로소 선언될 당시의 LexicalEnvironment를 들여다 볼 일이 생긴다는 뜻
var outer = function() {
var a = 1;
var inner = function() {
console.log(++a); // 2
};
inner();
};
outer();
1) outer 함수에서는 변수 a를 선언함
2) inner함수에서는 a를 선언하지 않고 a의 값을 1만큼 증가시키고 그 값을 출력함
3) inner함수에서 a를 선언하지 않았으므로 environmentRecord에서 값을 찾지 못함
4) outerEnvironmentReference를 뒤지기 시작 => 여기에서 상위 컨텍스트인 outer의 LexicalEnvironment에 접근해서 a를 찾는다
5) outer 함수가 종료되면 LexicalEnvironment에 저장된 식별자들(a, inner)에 대한 참조를 지움
6) 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수집 대상이 됨 🧹🧹🧹🧹🧹
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
1) inner함수 내부에서 외부 변수 a를 사용함
2) 6번째 줄에서 inner함수를 실행한 결과가 아니라 inner함수 자체를 리턴하고 있음
3) outer 함수가 종료될 때(outer함수의 실행 컨텍스트가 종료될 때) outer2 변수는 outer의 실행결과인 inner함수를 참조하게 됨
4) outer2 함수가 호출될 때 앞서 반환된 함수 inner가 실행됨.
1) outer2를 통해서 inner함수가 실행될 때 outer 함수는 이미 종료된 상태이다
2) 어떻게 outer 함수의 LexicalEnvironment에 접근하는 거지? ==> 가비지 컬렉터 동작 방식 때문
outer함수가 실행 종료되어도 inner함수는 호출될 가능성이 있으므로 수집 대상에서 제외됨!
그래서 클로저가 뭔데?
클로저는 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이다.
방금 전 예시처럼,
함수의 실행 컨텍스트가 종료된 후에도 LexicalEnvironment가 가비지 컬렉션 대상이 되지 않는 경우는 지역 변수를 참조하는 내부함수가 외부로 전달된 경우가 유일
클로저와 메모리 관리
클로저는 의도적으로 함수의 지역변수가 계속 쓸일이 있게 만듦으로써 메모리를 소모하도록 하면서 발생하는 현상이다. 이로 인해 메모리 누수(memory leak) 위험 문제가 제기된다.
메무리 누수 위험을 근거로 클로저 사용을 조심해야한다는 주장들이 있다고 한다 👀
하지만 이를 다시 거꾸로 말하면,
지역변수의 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 처리를 해주면 클로저의 문제점이 사라진다는 말과 같다!! === 참조하는 카운트를 0으로 만들면 가비지 컬렉팅을 해갈테니까!
참조 카운트 0으로 만드는 방법
// return에 의한 클로저의 메모리 해제
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer(); // inner
console.log(outer()); // inner
console.log(outer()); // inner
outer = null // outer 식별자의 inner 함수 참조를 끊음
클로저 활용 사례
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');
fruits.forEach(function (fruit) { // (A)
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', function () { // (B)
alert('your choice is' + fruit);
});
$ul.appendChild($li);;
});
document.body.appendChild($ul);
위 경우 (B)의 함수를 변수에 담아서 사용할 수도 있다.
var alertFruit = function(fruit) {
alert('your choice is' + fruit);
};
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');
fruits.forEach(function (fruit) { // (A)
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruit);
$ul.appendChild($li);;
});
document.body.appendChild($ul);
alertFruit(fruits[1]) // your choise is [object PointerEvent]
"your choice is [object PointerEvent]"이 출력되는 이유는?
콜백함수의 인자에 대한 제어권은 누가 갖고 있다? ===> addEventListener
addEventListener는 콜백함수를 호출할 때 첫 번째 인자에 '이벤트 객체'를 주입하기 때문이다.
어떻게 문제 해결하면 될까?
고차함수로 해결하기
고차함수: 다른 함수를 인자로 받거나 함수를 반환하는 함수
var alertFruitBuilder = function(fruit) {
return function() {
alert('your choice is' + fruit);
} // alertFruit
};
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');
fruits.forEach(function (fruit) {
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruitBuilder(fruit);
$ul.appendChild($li);;
});
document.body.appendChild($ul);
alertFruit(fruits[1])