# [5장] 클로저

# 1. 클로저의 의미 및 원리 이해

**클로저**는 함수와 그 함수가 선언될 당시의 `Lexical environment`의 상호관계에 따른 현상이다.

여기서 '선언될 당시의 `Lexical environment`'란 함수가 정의된 위치에 따라 결정되는 변수와 스코프의 범위를 의미한다. 함수의 실행 컨텍스트에는 `outerEnvironmentReference`가 포함되는데, 이는 바로 이 `Lexical environment`을 참조한다.

이러한 맥락에서, 어떤 함수 A 내부에서 선언된 함수 B는 A의 `Lexical environment`에 접근할 수 있다. 즉, B는 A에서 선언된 변수들에 접근 가능하지만, A는 B에서 선언된 변수들에 접근할 수 없다. 이처럼 B가 A의 `Lexical environment`을 **기억**하고 있기 때문에 클로저가 형성되는 것이다.

![](https://i.imgur.com/E6ulnLw.png)
_이미지 출처: "코어 자바스크립트", 정재남, 인프런, 클로저 (closure)_

결론적으로, 클로저는 컨텍스트 A에서 선언한 변수를 내부함수 B에서 참조할 경우에 발생하는 특별한 현상이라고 할 수 있다.

<br>

다음 예제를 통해 자세히 알아보자.

#### 외부 함수의 변수를 참조하는 내부 함수(1)

```js
var outer = function () {
  var a = 1;
  var inner = function () {
    console.log(++a); // 2
  };
  inner();
};
outer();
```

- `inner`함수 내부에서는 `a`를 선언하지 않았기 때문에 `environmentRecord`에서 값을 찾지 못한다.
- `outerEnvironmentReference`에 지정된 상위 컨텍스트인 `outer`의 `LexicalEnvironment`에 접근해 다시 `a`를 찾는다.
- `inner`함수의 실행 컨텍스트가 종료되고, `outer`함수의 실행 컨텍스트가 종료되면 `LexicalEnvironment`에 저장된 식별자들(`a, inner`)에 대한 참조를 지운다.
  그러면 각 주소에 저장되어 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 GC의 수집 대상이 될 것이다.

<br>

예제의 콜스택 및 실행 컨텍스트를 도식화하면 아래와 같다.
![](https://i.imgur.com/WdEyLER.png)

`outer` 컨텍스트에서 선언한 변수를 내부함수 `inner`에서 참조하고는 있지만, 일반적인 함수 및 내부함수에서의 동작으로, 별다른 특별한 현상은 보이지 않는 것 같다.

<br>

다음 예제를 보자

#### 외부 함수의 변수를 참조하는 내부 함수(2)

```js
01 var outer = function() {
02   var a = 1;
03   var inner = function() {
04     return ++a;
05   };
06   return inner();
07 };
08 var outer2 = outer();
09 console.log(outer2); // 2
```

- 6번째 줄에서 `inner`함수를 실행한 결과를 리턴하고 있으므로 결과적으로 `outer`함수의 실행 컨텍스가 종료된 시점에는 `a`변수를 참조하는 대상이 없어진다.
- 마찬가지로 `a, inner` 변수의 값들은 언젠가 GC에 의해 소멸될 것이다.

이 예제 역시 일반적인 함수 및 내부함수에서의 동작과 차이가 없다.

두 예제 모두 `outer`함수의 실행 컨텍스트가 종료되기 이전에 `inner`함수의 실행 컨텍스트가 종료되어 있으며, 이후 별도로 `inner`함수를 호출할 수 없다는 공통점이 있다.

<br>

그렇다면 `outer`의 실행 컨텍스트가 종료된 후에도 `inner`함수를 호출할 수 있게 만들면 어떨까?

#### 외부 함수의 변수를 참조하는 내부 함수(3)

```js
01 var outer = function() {
02  var a = 1;
03  var inner = function() {
04    return ++a;
05  }
06  return inner;
07 };
08 var outer2 = outer();
09 console.log(outer2()); // 2
10 console.log(outer2()); // 3
```

- 6번째 줄에서 함수의 실행 결과가 아닌 `inner`함수를 리턴하고 있다.
- 8번째 줄에서 `outer`함수의 실행 컨텍스트가 종료되었지만, `outer2`변수는 `outer`의 실행 결과인 `inner`함수를 참조하고 있다.
- 9번째 줄에서 `outer2`를 실행하면 `inner`함수의 실행 컨텍스트가 생성되고, `outer`함수의 `LexicalEnvironment`에 접근해 `a`를 찾아 `++`연산을 수행한 후 `inner`함수의 실행 컨텍스트가 종료된다.
- 10번째 줄에서도 같은 방식으로 `a`의 값을 2에서 3으로 증가시킨후 반환한다.

가비지 컬렉터는 `outer`함수의 실행 컨텍스트가 종료된 후에도 `inner`함수를 참조하는 `outer2`변수가 존재하기 때문에 `inner`함수의 실행 컨텍스트를 소멸시키지 않는다.

<br>

즉, **컨텍스트 A**에서 선언한 `변수 a`를 참조하는 **내부함수 B**를 **A**의 외부로 전달할 경우, **A**가 종료된 이후에도 `a`가 소멸되지 않고 유지되는 현상이 발생한다.

이러한 현상을 **클로저**라고 한다.

<br>

한 가지 주의할 점은, '외부로 전달'하는 것이 `return`만을 의미하는 것은 아니라는 것이다.

#### return 없이도 클로저가 발생하는 다양한 경우

**(1) setInterval / setTimeout**

```js
(function () {
  var a = 0;
  var intervalId = null;
  var inner = function () {
    if (++a >= 10) {
      clearInterval(intervalId);
    }
    console.log(a);
  };
  intervalId = setInterval(inner, 1000);
})();
```

- 별도의 외부객체인 `window`의 메서드 `setInterval`이 `inner`함수를 호출하고, `inner`함수는 지역변수 `a`를 참조하고 있다.

**(2) eventListener**

```js
(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);
})();
```

- 별도의 외부객체인 DOM의 메서드 `addEventListener`가 `click`이벤트가 발생하면 내부함수를 호출하고, 내부함수는 지역변수 `count`를 참조하고 있다.

<br>

두 상황 모두 지역변수를 참조하는 내부함수를 외부로 전달하고 있으므로 클로저가 발생한다.

<br>
<br>

# 2. 클로저와 메모리 관리

클로저는 객체지향과 함수형 모두를 아우르는 매우 중요한 개념이다. 메모리 누수의 위험을 이유로 클로저를 사용하는 것을 꺼리는 경우가 있지만, 의도대로 메모리를 더이상 소모하지 않도록 클로저를 해제하는 방법을 알고 있다면 안심하고 사용할 수 있다.

#### 클로저의 메모리 관리

**(1) return에 의한 클로저의 메모리 해제**

```js
var outer = (function () {
  var a = 1;
  var inner = function () {
    return ++a;
  };
  return inner;
})();
console.log(outer());
console.log(outer());
outer = null; // outer 식별자의 inner 함수 참조를 끊음
```

**(2) setInterval에 의한 클로저의 메모리 해제**

```js
(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에 의한 클로저의 메모리 해제**

```js
(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);
})();
```

<br>
<br>

# 3. 클로저 활용 사례

## 3-1 콜백 함수 내부에서 외부 데이터를 사용하고자 할 때

#### 콜백 함수와 클로저(1)

```js
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);
```

- `fruits`변수를 순회하며 각 과일별로 `li`태그를 생성하고, `li`태그를 클릭했을 때 해당 과일명을 알림창으로 띄우는 코드이다.
- **(A)**
  - 내부에서 외부 변수를 사용하지 않고 있으므로 클로저가 발생하지 않는다.
  - `fruits`의 개수만큼 실행되며, 그때마다 새로운 실행 컨텍스트가 생성된다.
- **(B)**
  - 내부에서 외부 변수인 `fruit`를 참조하고 있으므로 클로저가 발생한다.
  - (A)의 실행 종료 여부와 무관하게 클릭 이벤트에 의해 (B)의 `outerEnvironmentReference`가 (A)의 `LexicalEnvironment`를 참조하므로 변수 `fruit`는 계속 유지된다.

<br>

만약, (B)함수의 쓰임새가 콜백 함수에 국한되지 않는 경우라면 반복을 줄이기 위해 (B)를 외부로 분리하는 것이 나을 수 있을 것이다.

다음과 같이 `fruit`를 인자로 받아 출력하는 형태로 변경해보자.

#### 콜백 함수와 클로저(2) - 콜백 함수를 외부로 꺼내는 방법

```js
var fruits = ["apple", "banana", "peach"];
var $ul = document.createElement("ul");

var alertFruit = function (fruit) {
  alert("your choice is " + fruit);
};

fruits.forEach(function (fruit) {
  var $li = document.createElement("li");
  $li.innerText = fruit;
  $li.addEventListener("click", alertFruit);
  $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);
```

- 콜백 함수를 외부로 꺼내 `alertFruit`라는 변수에 저장하고, 직접 실행할 수 있도록 하였다.
- 마지막 줄에서는 정상적으로 `'banana'`에 대한 알림창이 뜨는 것을 확인할 수 있다.

<br>

그러나 각 `li`를 클릭했을 때는 클릭한 대상의 과일명이 아닌 `[object MouseEvent]`가 출력되는데, 이는 `addEventListener`가 콜백 함수를 호출할 때 첫 번째 인자로 `event`객체를 전달하기 때문이다.

이 문제는 `bind`메서드로 해결할 수 있다.

#### 콜백 함수와 클로저(3) - `bind`메서드를 활용하는 방법

```js
var fruits = ["apple", "banana", "peach"];
var $ul = document.createElement("ul");

var alertFruit = function (fruit) {
  alert("your choice is " + fruit);
};

fruits.forEach(function (fruit) {
  var $li = document.createElement("li");
  $li.innerText = fruit;
  $li.addEventListener("click", alertFruit.bind(null, fruit));
  $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);
```

다만 이렇게 하면 이벤트 객체가 인자로 넘어오는 순서가 바뀌며, 함수 내부에서의 `this`가 의도와 다르게 동작할 수 있으므로 주의해야 한다.

<br>

이러한 문제가 발생하지 않도록 하려면, `bind`메서드 대신 고차함수를 활용하면 된다.

#### 콜백 함수와 클로저(4) - 고차함수를 활용하는 방법

```js
01 var fruits = ["apple", "banana", "peach"];
02 var $ul = document.createElement("ul");
03
04 var alertFruitBuilder = function(fruit) {
05   return function() {
06     alert("your choice is " + fruit);
07   }
08 };
09
10 fruits.forEach(function(fruit) {
11   var $li = document.createElement("li");
12   $li.innerText = fruit;
13   $li.addEventListener("click", alertFruitBuilder(fruit));
14   $ul.appendChild($li);
15 });
16 document.body.appendChild($ul);
17 alertFruit(fruits[1]);
```

- 4번째 줄에서 `alertFruitBuilder`라는 함수를 정의하고, 내부에서 `fruit`를 인자로 받아 알림을 띄우는 익명함수를 반환하도록 하였다.
- 12번째 줄에서 `alertFruitBuilder`를 실행하면서 `fruit`를 인자로 넘겨주고, 반환된 함수를 리스너에 콜백함수로 전달할 것이다.
  - 언젠가 클릭 이벤트가 발생하면 이 함수의 실행 컨텍스트가 생성되고, `alertFruitBuilder`의 인자로 넘어온 `fruit`를 `outerEnvironmentReference`를 통해 참조할 수 있을 것이다.
  - 즉, `alertFruitBuilder`의 실행결과로 반환된 함수는 클로저가 형성되어 있다.

<br>

위 세 가지 방법의 장단점에 따라 적절한 방법을 선택하여 사용하면 된다.

<br>

## 3-2 접근 권한 제어(정보 은닉)

**정보 은닉**은 어떤 모듈의 내부 로직에 대해 외부에서 접근할 수 없도록 하는 것이다.

접근 권한에는 `public, private, protected`가 있는데, `public`은 외부에서 접근 가능한 것을 의미하고, `private`는 외부에서 접근 불가능한 것을 의미한다.

자바스크립트는 기본적으로 변수에 대한 접근 권한을 제어할 수 있는 기능을 제공하지 않지만, 클로저를 활용하면 이러한 기능을 구현할 수 있다.

```js
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`함수를 종료할 때 `inner`함수를 반환함으로써 `outer`함수의 지역변수인 `a`의 값을 외부에서도 읽을 수 있게 되었다.

이처럼 클로저를 활용하면 외부에서 함수 내부의 변수들 중 선택적으로 일분의 변수에 대한 접근 권한을 부여할 수 있다.

<br>

**closure**은 사전적으로 '닫혀있음, 폐쇄성, 완결성'을 의미한다.

`outer`함수는 외부(전역 스코프)로부터 격리된 닫힌 공간이다. 외부에서는 외부에 노출되어 있는 `outer`라는 변수를 통해 `outer`함수를 실행할 수는 있지만, `outer`함수 내부에는 어떠한 개입도 할 수 없다. 외부에서는 오직 `outer`함수가 `return`한 정보에만 접근할 수 있다.

따라서, 외부에 제공하고자 하는 정보만을 `return`하고, 그 외의 정보는 외부로부터 완전히 차단하는 것으로 접근 권한 제어가 가능하다.

<br>

간단한 게임을 만들면서 접근 권한을 제어해보자.

### 자동차 경주 게임

```
규칙
- 각 턴마다 주사위를 굴려 나온 숫자(km)만큼 이동
- 차량별로 연료량(fuel)과 연비(power)는 무작위로 생성
- 남은 연료가 이동할 거이레 필요한 연료보다 부족하면 이동하지 못함
- 모든 유저가 이동할 수 없는 턴에 게임이 종료됨
- 게임 종료 시점에 가장 멀리 이동해 있는 사람이 승리
```

<br>

#### 간단한 자동차 객체

```js
var car = {
  fuel: Math.ceil(Math.random() * 10 + 10), // 연료(L)
  power: Math.ceil(Math.random() * 3 + 2), // 연비(km/L)
  moved: 0, // 총 이동거리(km)
  run: function () {
    var km = Math.ceil(Math.random() * 6);
    var wasteFuel = km / this.power;
    if (this.fuel < wasteFuel) {
      console.log("이동 불가");
      return;
    }
    this.fuel -= wasteFuel;
    this.moved += km;
    console.log(km + "km 이동 (총 " + this.moved + "km)");
  },
};
```

- `fuel`과 `power`는 무작위로 생성된다.
- `run`메서드를 실행할 때마다 무작위로 생성된 `km`만큼 이동한다.
- 이동에 필요한 연료량인 `wasteFuel`을 계산하여 `fuel`을 차감하고, 만약 `fuel`이 부족하면 이동하지 못한다.

이제 `car`객체를 사람 수만큼 생성해서 각자의 턴에 `run`메서드를 실행하면 게임이 진행될 것이다.

<br>

그런데 이때, 자바스크립트를 다룰 줄 아는 사람이 있다면 무작위로 정해지는 연료, 연비, 이동거리 등을 마음대로 바꿀 수 있을 것이다.

```js
// 치트키
car.fuel = 10000;
car.power = 100;
car.moved = 1000;
```

<br>

이러한 문제를 방지하기 위해 클로저를 활용하여 접근 권한을 제어해보자.

#### 클로저 변수를 보호한 자동차 객체(1)

```js
var createCar = function () {
  var fuel = Math.ceil(Math.random() * 10 + 10); // 연료(L)
  var power = Math.ceil(Math.random() * 3 + 2); // 연비(km/L)
  var moved = 0; // 총 이동거리(km)
  return {
    get moved() {
      return moved;
    },
    run: function () {
      var km = Math.ceil(Math.random() * 6);
      var wasteFuel = km / power;
      if (fuel < wasteFuel) {
        console.log("이동 불가");
        return;
      }
      fuel -= wasteFuel;
      moved += km;
      console.log(km + "km 이동 (총 " + moved + "km). 남은 연료: " + fuel);
    },
  };
};
var car = createCar();
```

- `createCar`함수를 실행하여 객체를 생성하게 했다.
- `fuel, power`변수는 외부에서의 접근을 제한했고, `moved`변수는 `getter`메서드를 통해 읽기만 가능하게 했다.

<br>

이제 외부에서는 `run`메서드를 실행하는 것과 현재의 `moved`값을 읽는 것만 가능하다.
![](https://i.imgur.com/F2Bd6Cx.png)
![](https://i.imgur.com/KBj18Sh.png)

<br>

그러나 아직 `run`메서드를 다른 내용으로 덮어씌우는 어뷰징이 가능한 상태이다.
![](https://i.imgur.com/KnhO9jG.png)

<br>

이런 어뷰징까지 막기 위해서는 객체를 `return`하기 전에 미리 변경할 수 없게 만들어야 한다.

#### 클로저로 변수를 보호한 자동차 객체(2)

```js
var createCar = function () {
  var fuel = Math.ceil(Math.random() * 10 + 10); // 연료(L)
  var power = Math.ceil(Math.random() * 3 + 2); // 연비(km/L)
  var moved = 0; // 총 이동거리(km)
  var publicMembers = {
    get moved() {
      return moved;
    },
    run: function () {
      var km = Math.ceil(Math.random() * 6);
      var wasteFuel = km / power;
      if (fuel < wasteFuel) {
        console.log("이동 불가");
        return;
      }
      fuel -= wasteFuel;
      moved += km;
      console.log(km + "km 이동 (총 " + moved + "km). 남은 연료: " + fuel);
    },
  };
  Object.freeze(publicMembers);
};
var car = createCar();
```

- `publicMembers`라는 객체를 생성하여 `getter`메서드와 `run`메서드를 담았다.
- `Object.freeze`메서드를 통해 `publicMembers`객체를 불변하게 만들었다.

<br>

> **클로저를 활용해 접근권한을 제어하는 방법**을 정리하자면 다음과 같다.
>
> 1. 함수에서 지역변수 및 내부함수 등을 생성한다.
> 2. 외부에 접근권한을 주고자 하는 대상들로 구성된 참조형 데이터(대상이 여럿일 때는 객체 또는 배열, 하나일 때는 함수)를 `return`한다.
>    -> `return`한 변수들은 공개 멤버가 되고, 그렇지 않은 변수들은 비공개 멤버가 된다.

<br>

## 3-3 부분 적용 함수

**부분 적용 함수**란, 함수의 인자를 일부만 넘겨 기억시켜두고, 나중에 나머지 인자를 넘겨 실행하는 함수를 의미한다.

#### bind 메서드를 활용한 부분 적용 함수

```js
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
```

- `add`함수를 실행할 때 `1, 2, 3, 4, 5`를 인자로 넘겨 실행한 결과를 `addPartial`에 저장한다.
- `addPartial`을 실행할 때 `6, 7, 8, 9, 10`을 인자로 넘겨 실행하면, `add`함수에는 `1, 2, 3, 4, 5, 6, 7, 8, 9, 10`이 넘어가게 되어 `result`에는 `55`가 저장된다.

`add`함수는 `this`를 사용하지 않으므로 `bind`메서드를 사용해도 문제가 없다. 그러나, `this`의 값을 변경할 수 밖에 없기 때문에 메서드에서는 사용할 수 없을 것이다.

<br>

만약, `this`에 관여하지 않는 별도의 부분 적용 함수가 있다면 범용성을 높일 수 있을 것이다.

#### 부분 적용 함수 구현(1)

```js
var partial = function () {
  var originalPartialArgs = arguments;
  var func = originalPartialArgs[0];
  if (typeof func !== "function") {
    throw new Error("첫 번째 인자가 함수가 아닙니다.");
  }
  return function () {
    var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
    var restArgs = Array.prototype.slice.call(arguments);
    return func.apply(this, partialArgs.concat(restArgs));
  };
};

var add = function () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};
var addPartial = partial(add, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55

var dog = {
  name: "강아지",
  greet: partial(function (prefix, suffix) {
    return prefix + this.name + suffix;
  }, "왈왈, "),
};
dog.greet("입니다!"); // 왈왈, 강아지입니다!
```

- 첫 번째 인자에는 원본 함수를, 두 번째 인자부터는 부분 적용할 인자들을 전달하고, 반환할 함수(부분 적용 함수)에서는 나머지 인자들을 받아 이들을 한데 모아(`concat`) 원본 함수를 호출(`apply`)한다.
- 실행 시점의 `this`를 그대로 반영하여 `this`에는 아무런 영향을 주지 않는다.

의도대로 잘 동작하지만, 부분 적용 함수에 넘길 인자를 앞에서부터 순차적으로 넘겨야 한다는 아쉬움이 있다.

<br>

만약 인자들을 원하는 위치에 미리 넣어놓고 나중에는 빈 자리에 인자를 채워넣어 실행할 수 있다면, 코드의 재사용성이 높아질 것이다.

#### 부분 적용 함수 구현(2)

```js
Object.defineProperty(window, "_", {
  value: "EMPTY_SPACE",
  writable: false,
  configurable: false,
  enumerable: false,
});

var partial2 = function () {
  var originalPartialArgs = arguments;
  var func = originalPartialArgs[0];
  if (typeof func !== "function") {
    throw new Error("첫 번째 인자가 함수가 아닙니다.");
  }
  return function () {
    var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
    var restArgs = Array.prototype.slice.call(arguments);
    for (var i = 0; i < partialArgs.length; i++) {
      if (partialArgs[i] === _) {
        partialArgs[i] = restArgs.shift();
      }
    }
    return func.apply(this, partialArgs.concat(restArgs));
  };
};

var add = function () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};
var addPartial = partial2(add, 1, 2, _, 4, 5, _, _, 8, 9);
console.log(addPartial(3, 6, 7, 10)); // 55

var dog = {
  name: "강아지",
  greet: partial2(function (prefix, suffix) {
    return prefix + this.name + suffix;
  }, "왈왈, "),
};
dog.greet(" 배고파요!"); // 왈왈, 강아지 배고파요!
```

- **전역 식별자 \_ 정의**
  - `Object.defineProperty` 함수를 사용하여 전역 객체인 `window`에 `_`라는 식별자를 추가한다. 이 `_`는 빈 자리를 나타내는 값으로 사용된다.
  - `writable, configurable, enumerable` 속성을 `false`로 설정하여 `_`가 수정, 재정의, 열거되지 않도록 한다.
- **부분 적용함수 partail2 정의**
  - 첫 번째 인자로 받은 함수를 `func`에 저장하고, 나머지 인자들을 `partialArgs`에 저장한다.
  - 반환되는 익명 함수는 `partialArgs`와 `restArgs`를 합쳐 `func`를 실행한다.
  - 이때, `partialArgs`의 값이 `_`이면 `restArgs`에서 값을 꺼내어 채워넣는다.

이렇게 하면 인자들을 원하는 위치에 미리 넣어놓고 나중에는 빈 자리에 인자를 채워넣어 실행할 수 있게 된다.

<br>

앞선 예제의 부분 적용 함수들은 클로저를 핵심 기법으로 사용하고 있다. 미리 일부 인자를 넘겨두어 기억하게 하고, 추후 필요한 시점에 기억했던 인자들까지 함께 실행하게 한다는 개념이 클로저의 정의에 부합한다.

<br>

다음으로는, 실무에서 부분 함수를 사용하기에 적합한 예로 디바운스를 알아보자.

**디바운스**는 짧은 시간동안 이벤트가 많이 발생할 경우, 이벤트를 그룹화하여 마지막 이벤트만 발생하도록 하는 기술이다. `scroll, wheel, mousemove, resize`등에 적용하기 좋다.

#### 부분 적용 함수 - 디바운스

```js
01 var debounce = function (eventName, func, wait) {
02   var timeoutId = null;
03   return function (event) {
04     var self = this;
05     console.log(eventName, "event 발생");
06     clearTimeout(timeoutId);
07     timeoutId = setTimeout(func.bind(self, event), wait);
08   };
09 };
10
11 var moveHandler = function (e) {
12   console.log("move event 처리");
13 };
14 var wheelHandler = function (e) {
15   console.log("wheel event 처리");
16 };
17 document.body.addEventListener("mousemove", debounce("move", moveHandler, 500));
18 document.body.addEventListener(
19   "mousewheel",
20   debounce("wheel", wheelHandler, 700)
21 );
```

- 출력 용도로 지정한 `eventName`과 실행할 함수 `func`, 마지막으로 발생한 이벤트인지 확인하기 위한 대기시간 `wait`을 인자로 받는다.
- 내부에서는 `timeoutId`변수를 생성하고, 클로저로 `EventListner`에 의해 호출된 함수를 반환한다.
- 반환될 함수 내부에서는, 4번째 줄에서 `setTimeout`을 사용하기 위해 `this`를 `self`에 저장하고, 6번째 줄에서 무조건 대기큐를 초기화한다.
- 7번째 줄에서 `setTimeout`을 사용하여 `func`함수를 `wait`시간 후에 실행한다.

이제 최초 이벤트가 발생하면 7번째 줄에 의해 타임아웃의 대기열에 '`wait`시간 뒤에 `func`를 실행할 것'이라는 내용이 담긴다. 그런데 `wait`시간이 경과하기 이전에 다시 동일한 이벤트가 발생하면 6번째 줄에 의해 타임아웃의 대기열이 초기화되고, 새로운 `wait`시간이 시작된다.

결국, 각 이벤트가 바로 이전 이벤트로부터 `wait`시간 이내에 발생하는 한 마지막에 발생한 이벤트만이 초기화되지 않고 무사히 실행될 것이다.

참고로 위 예제에서 클로저로 처리되는 변수에는 `eventName, func, wait, timeoutId`가 있다.

<br>

> **참고**
> ES5 환경에서는 `_`를 '비워놓음'으로 사용하기 위해 어쩔 수 없이 전역공간을 침범했다. ES6에서는 `Symbol.for`을 사용하여 이러한 문제를 해결할 수 있다.
> `Symbol.for`메서드는 전역 심볼공간에 인자로 넘어온 문자열이 이미 있으면 해당 값을 참조하고, 선언되어 있지 않으면 새로 만드는 방식으로, 어디서든 접근 가능하면서 유일무이한 상수를 만들고자 할 때 적합하다.
>
> ```js
> (function () {
>   var EmptySpace = Symbol.for("EMPTY_SPACE");
>   // 기존 전역 심볼공간에 'EMPTY_SPACE'라는 문자열을 가진 심볼이 없으므로 새로 생성
>   console.log(EmptySpace); // Symbol(EMPTY_SPACE)
> })();
>
> (function () {
>   // 기존 전역 심볼공간에 'EMPTY_SPACE'라는 문자열의 심볼이 있으므로 해당 값을 참조.
>   console.log(EmptySpace); // Symbol(EMPTY_SPACE)
> })();
> ```
>
> 이 `Symbol.for`을 이용하면 예제 [부분 적용 함수 구현(2)](#heading-2-4)를 다음과 같이 변경할 수 있다.
>
> ```js
> Object.defineProperty(window, "_", {
>   value: "EMPTY_SPACE",
>   writable: false,
>   configurable: false,
>   enumerable: false,
> });
>
> var partial2 = function () {
>   var originalPartialArgs = arguments;
>   var func = originalPartialArgs[0];
>   if (typeof func !== "function") {
>     throw new Error("첫 번째 인자가 함수가 아닙니다.");
>   }
>   return function () {
>     var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
>     var restArgs = Array.prototype.slice.call(arguments);
>     for (var i = 0; i < partialArgs.length; i++) {
>       // 변경한 부분
>       if (partialArgs[i] === Symbol.for("EMPTY_SPACE")) {
>         partialArgs[i] = restArgs.shift();
>       }
>     }
>     return func.apply(this, partialArgs.concat(restArgs));
>   };
> };
> // ... 생략 ...
> // 추가된 부분
> var _ = Symbol.for("EMPTY_SPACE");
> var addPartial = partial3(add, 1, 2, _, 4, 5, _, _, 8, 9);
> console.log(addPartial(3, 6, 7, 10)); // 55
> ```

<br>

## 3-4 커링 함수

**커링 함수**란, 여러 개의 인자를 받는 함수를 단 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 함수를 의미한다.

커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 한다. 또한, 중간 과정상의 함수를 실행한 결과는 그다음 인자를 받기위해 대기만 할 뿐, 마지막 인자가 전달되기 전까지는 실행되지 않는다.

여러 개의 인자를 전달할 수 있고, 실행 결과를 재실행할 때 원본 함수가 무조건 실행되는 부분 적용 함수와는 다르다.

#### 커링 함수(1)

```js
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

var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8)); // 8
console.log(getMinWith10(25)); // 10
```

- **`curry3`함수** :
  - 하나의 함수(`func`)를 인자로 받는다.
  - 첫 번째 함수는 `a`를 인자로 받고, 그 안에서 두 번째 함수를 반환한다.
  - 두 번째 함수는 `b`를 인자로 받고, `func`함수를 `a`와 `b`를 인자로 하여 호출한다.
- **`getMaxWith10`함수** :
  - `curry3`에 `Math.max`함수를 전달하여 생성되며, 첫 번째 인자로 10을 고정한다.
  - 이제 `getMaxWith10`함수는 하나의 인자만 받아 `Math.max(10, x)`와 같은 방식으로 작동한다.
- **`getMinWith10`함수** :
  - `curry3`에 `Math.min`함수를 전달하여 생성되며, 첫 번째 인자로 10을 고정한다.
  - 이제 `getMinWith10`함수는 하나의 인자만 받아 `Math.min(10, x)`와 같은 방식으로 작동한다.

<br>

이처럼 필요한 인자 개수만큼 함수를 만들어 계속 리턴하다가 마지막에 조합된 함수를 실행하면 되기 때문에 유연하게 사용할 수 있지만, 인자가 많아질수록 가독성이 떨어진다는 단점이 있다.

<br>

#### 커링 함수(2)

```js
var curry5 = function (func) {
  return function (a) {
    return function (b) {
      return function (c) {
        return function (d) {
          return function (e) {
            return func(a, b, c, d, e);
          };
        };
      };
    };
  };
};
var getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5));
```

<br>

ES6에서는 화살표 함수를 사용하여 더 간결하게 표현할 수 있다.

```js
var curry5 = (func) => (a) => (b) => (c) => (d) => (e) => func(a, b, c, d, e);
```

<br>

화살표 함수로 커링 함수를 구현하면 순서에 따라 함수에 값을 차례로 넘겨주고, 마지막에 `func`가 호출되는 흐름을 한눈에 파악할 수 있다. 각 단계에서 받은 인자들을 모두 마지막 단계에서 참조하기 때문에 GC되지 않고 클로저가 형성되며, 마지막 호출로 실행 컨텍스트가 종료된 후에야 GC의 수거 대상이 된다.

커링 함수는 원하는 시점까지 지연시켰다가 실행하는 것이 필요한 상황에 유용하게 사용할 수 있다.

```js
var getInformation = function (baseUrl) {
  // 서버에 요청할 주소의 기본 URL
  return function (path) {
    // path 값
    return function (id) {
      // id 값
      return fetch(baseUrl + path + id); // 실제 서버에 정보를 요청
    };
  };
};
```

```js
// ES6
var getInformation = (baseUrl) => (path) => (id) => fetch(baseUrl + path + id);
```

<br>

HTML5의 `fetch`함수는 `url`을 받아 해당 `url`에 HTTP 요청을 한다. 보통 REST API를 이용할 경우 `baseUrl`은 몇 개로 고정되지만, 나머지 `path`나 `id`값은 매우 많을 수 있다.

이때, 매번 `baseUrl`을 넣어주는 것은 비효율적이므로 커링 함수를 사용하여 `baseUrl`을 미리 넣어두고, 나머지 `path`와 `id`값을 나중에 넣어주는 것이 효율적이다.

```js
var imageUrl = "http://imageAddress.com/";

// 이미지 타입별 요청 함수 준비
var getImage = getInformation(imageUrl); // http://imageAddress.com/
var getEmotion = getImage("emotion"); // http://imageAddress.com/emotion
var getIcon = getImage("icon"); // http://imageAddress.com/icon

// 실제 요청
var emotion1 = getEmotion(100); // http://imageAddress.com/emotion/100
var emotion2 = getEmotion(102); // http://imageAddress.com/emotion/102
var icon1 = getIcon(205); // http://imageAddress.com/icon/205
var icon2 = getIcon(234); // http://imageAddress.com/icon/234
```

<br>

이러한 이유로 여러 프레임워크나 라이브러리에서 커링을 광범위하게 사용하고 있다.

Redux의 미들웨어로 예를들면 다음과 같다.

```js
// Redux Middleware 'Logger'
const logger = (store) => (next) => (action) => {
  console.log("dispatching", action);
  console.log("next state", store.getState());
  return next(action);
};

// Redux Middleware 'Thunk'
const thunk = (store) => (next) => (action) => {
  return typeof action === "function"
    ? action(dispatch, store.getState)
    : next(action);
};
```

이 두 미들웨어는 공통적으로 `store, next, action`순서로 인자를 받는다. 이 중 `store`는 프로젝트 내에서 한 번 생성된 이후로는 바뀌지 않는 속성이고 `dispatch`의 의미를 가지는 `next`역시 마찬가지지만, `action`의 경우는 매번 달라진다.

그렇기때문에 `store`와 `next`값이 결정되면 Redux 내부에서 `logger` 또는 `thunk`에 `store, next`를 미리 넘겨서 반환된 함수를 저장시켜놓고, 이후에는 `action`값만 받아서 처리할 수 있게끔 한 것이다.

<br>
<br>

# 4. 정리

**클로저**란, 어떤 함수에서 선언한 변수를 참조하는 내부함수를 외부로 전달할 경우, 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상이다.

내부함수를 외부로 전달하는 방법에는 함수를 `return`하는 경우뿐 아니라 콜백으로 전달하는 경우도 포함된다.

클로저는 그 본질이 메모리를 계속 차지하는 개념이므로, 더는 사용하지 않게 된 클로저에 대해서는 메모리를 차지하지 않도록 관리해줄 필요가 있다.

클로저는 다양한 곳에서 활용할 수 있는 중요한 개념이다.

