클로저
: 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
=> 지역변수를 참조하는 내부함수가 외부로 전달된 경우
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner; // inner 함수의 실행 결과가 아닌 inner 함수 자체를 반환
};
var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
=> outer 함수의 실행 컨텍스트가 종료 될 때 outer2의 변수는 outer의 실행 결과인 inner 함수를 참조하게 됨
=> inner 함수의 실행 컨텍스트의 envrionmentRecord에는 수집할 정보가 없으므로 스코프 체이닝에 따라 outerEnvionmentReference(= outer 함수의 LexicalEnvrionment)에 담긴 변수 a에 접근하여 1만큼 증가시킨 값을 반환 후 inner 함수의 실행 컨텍스트가 종료됨
💙 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않음
=> outer의 LexicalEnvrionment에 속하는 변수 중 변수 a가 가비지 컬렉터의 수집 대상에서 제외됨
예시)
// setInterval
(function () {
var a = 0;
var intervalId = null;
var inner = function () {
if (++a >= 10) {
clearInterval(intervalId);
}
console.log(a);
};
intervalId = setInterval(inner, 1000); // 콜백 함수 내부에서 지역변수 참조
})();
// eventListener
(function () {
var count = 0;
var button = document.createElement('button');
button.innerText = 'click';
button.addEventListener('click', function () {
console.log(++count, 'times clicked'); // handler 함수 내부에서 지역변수 참조
});
document.body.appendChild(button);
})();
메모리 관리
클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생
해결방법
- 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해줌
- 참조 카운트를 0으로 만듦 => 언젠가 GC가 수거 => 소모됐던 메모리 회수
참조 카운트를 0으로 만드는 방법
: 식별자에 참조형이 아닌 기본형(보통 null이나 undefined) 할당
// (1) return에 의한 클로저의 메모리 해제
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());
outer = null; // outer 식별자의 inner 함수 참조를 끊음
// (2) setInterval에 의한 클로저의 메모리 해제
(function () {
var a = 0;
var intervalId = null;
var inner = function () {
if (++a >= 10) {
clearInterval(intervalId);
inner = null; // inner 식별자의 함수 참조를 끊음
}
console.log(a);
};
intervalId = setInterval(inner, 1000);
})();
// (3) eventListener에 의한 클로저의 메모리 해제
(function () {
var count = 0;
var button = document.createElement('button');
button.innerText = 'click';
var clickHandler = function () {
console.log(++count, 'times clicked');
if (count >= 10) {
button.removeEventListener('click', clickHandler);
clickHandler = null; // clickHandler 식별자의 함수 참조를 끊음
}
};
button.addEventListener('click', clickHandler);
document.body.appendChild(button);
})();
클로저 활용
고차 함수
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');
var alertFruitBuilder = function (fruit) {
return function () {
alert('your choice is ' + fruit);
}
};
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]);
정보 은닉
: 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 함
클로저를 이용하여 함수 차원에서 public한 값과 private한 값을 구분하는 것이 가능
var outer = function () {
var a = 1;
var inner = function () {
return ++ a;
};
return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());
// outer 함수를 종료할 때 inner 함수를 반환함으로써
// outer 함수의 지역 변수인 a의 값을 외부에서도 읽을 수 있음 => public
=> 외부에 제공하고자 하는 정보들을 모아서 return하고(=> public), 내부에서만 사용할 정보들은 return하지 않는 것(=> private)으로 접근 권한 제어 가능
부분 적용 함수
: n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비소로 언래 함수의 실행 결과를 얻을 수 있게끔 하는 함수
디바운스
: 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것
=> 프론트엔드 성능 최적화에 큰 도움을 줌
var debounce = function (eventName, func, wait) {
var timeoutId = null;
return function (event) {
var self = this;
console.log(eventName, 'event 발생');
clearTimeout(timeoutId);
timeoutId = setTimeout(func.bind(self, event), wait)l
};
};
var moveHandler = function (e) {
console.log('move event 처리');
}
var wheelHandler = function (e) {
console.log('wheel event 처리');
};
document.body.addEventListener('mousemove', debounce('move', moveHandler, 500));
document.body.addEventListener('mousewheel', debounce('wheel', wheelHandler, 700));
// 클로저 처리되는 변수: eventName, func, wait, timeoutId
커링 함수
: 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것
: 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 함
var getInformation = function (baseUrl) {
return function (path) {
return function (id) {
return fetch(baseUrl + path + '/' + id);
};
};
};
// ES6
var getInformation = baseUrl => id => fetch(baseUrl + path + '/' + id);
var imageUrl = 'http://imageAddress.com/';
var getImage = getInfromation(imageUrl); // http://imageAddress.com
var getEmotion = getImage('emotion'); // http://imageAddress.com/emotion
var getIcon = getImage('icon'); // http://imageAddress.com/icon
var emotion = getEmotion(100); // http://imageAddress.com/emotion/100
var icon = getIcon(205); // http://imageAddress.com/icon/205