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 함수의 실행 시점에는 outer 함수는 이미 실행이 종료된 상태인데 outer 함수의 렉시컬환경에 어떻게 접근할 수 있는 걸까?
👉 가비지 컬렉터의 동작 방식 때문이다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않는다.
클로저란 어던 함수 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의 메서드에 전달할 콜백 함수 내부에서 지역변수를 참조하므로 클로저이다.
✔️ 메모리 누수와 관리 방법
메모리 누수 : 개발자의 의도와 달리 어떤 값의 참조 카운트가 0이 되지 않아 카비지 컬렉터의 수거 대상이 되지 않는 경우
관리 방법 : 클로저의 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해주면 된다. 즉 참조 카운트를 0으로 만들면 카비지 컬렉터가 메모리를 회수해간다.
❓ 어떻게 참조 카운트를 0으로 만들까?
👉 식별자에 참조형이 아닌 기본형 데이터(null이나 undefined)를 할당하면 된다.
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
console.log(outer2());
outer = null; // outer 식별자의 inner 함수 참조를 끊음
✔️ 활용 사례
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);
}
alertFruitBuilder의 실행 결과로 반환된 함수에는 클로저가 존재한다.
정보은닉 : 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나이다. 클로저를 이용하면 함수 차원에서 public한 값과 private한 값을 구분하는 것이 가능하다.
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2());
outer 함수를 종료할 때 inner 함수를 반환함으로써 outer 함수의 지역변수인 a의 값을 외부에서도 읽을 수 있게 됐다. 이처럼 클로저를 활용하면 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한을 부여할 수 있다.
외부에 제공하고자 하는 정보들을 모아서 return하고, 내부에서만 사용할 정보들은 return 하지 않는 것으로 접근 권한 제어가 가능하다. return한 변수들은 public, 그렇지 않은 변수들은 private이 된다.
부분 적용 함수란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수다.
var add = function () {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
};
var addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55
addPartial 함수는 인자 5개를 미리 적용하고, 추후 추가적으로 인자들을 전달하면 모든 인자를 모아 원래의 함수가 실행되는 부분 적용 함수이다.
// 부분 적용함수로 debounce 구현하기
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);
};
};
var moveHandler = function (e) {
console.log("move event 처리");
};
var wheelHandler = function (e) {
console.log("wheel event 처리");
};
document.body.addEventLister("mousemove", debounce("move", moveHandler, 500));
document.body.addEventLister(
"mousewheel",
debounce("wheel", wheelHandler, 700)
);
클로저로 처리되는 변수 : eventName, func, wait, timeoutId
커링 함수란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말한다. 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 하고 중간 과정상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기만 할 뿐으로, 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않는다.
var curry3 = function (func) {
return function (a) {
return function (b) {
return func(a, b);
};
};
};
var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25
원하는 시점까지 지연시켰다가 실행하는 것이 요긴한 상황이라면 커링을 쓰기에 적합하다. 혹은 프로젝트 내에서 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 적절한 후보가 된다.