Post

클로저 (Closure)

자바스크립트 클로저에 대해 정리한 페이지입니다.

클로저 (Closure)

Tags
JavaScript, Closure, Execution Context

개요

자바스크립트 클로저에 대해 정리한 페이지입니다.

클로저 (Closure)

클로저의 개념

클로저(Closure)란 외부 함수에서 선언한 변수를 참조하는 내부 함수를 외부로 전달할 경우, 외부 함수의 실행 컨텍스트가 종료된 이후에도 외부 함수에서 선언한 변수가 사라지지 않는 현상을 말합니다. 다시 말해, 클로저(Closure)는 함수가 생성될 때, 해당 함수가 선언된 환경의 스코프를 기억함으로써 외부 함수의 변수나 상태를 내부 함수에서 참조할 수 있는 기능을 의미합니다. 이 때, 외부 함수가 실행을 마치고 콜 스택에서 제거된 후에도 내부 함수는 여전히 외부 함수의 변수에 접근할 수 있습니다.

클로저의 동작 원리

클로저가 발생하는 원인은 가비지 컬렉터(Garbage Collector)의 동작 방식에 있습니다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않습니다. 예를 들어 다음과 같이 outer 함수의 변수 a를 참조하는 inner 함수가 있는 경우, inner 함수가 호출되어 inner 함수의 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer 함수의 LexicalEnvironment를 필요로 하므로 수집 대상에서 제외됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
const outer = function () {
  let a = 1;
  const inner = function () {
    return ++a;
  };

  return inner;
};

const outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3

inner 함수가 호출되어 inner 함수의 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer 함수의 LexicalEnvironment를 필요로 하므로 수집 대상에서 제외됩니다.

또한 클로저의 개념에서 언급한 “외부로 전달”이 return만을 의미하는 것은 아닙니다. 다음과 같이 별도의 외부 객체인 window의 메서드(setTimeout 또는 setInterval)에 전달할 콜백 함수 내부에서 myFunc 함수의 변수 a를 참조하는 경우 클로저가 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myFunc() {
  let a = 0;
  let timer = null;

  const inner = function () {
    if (++a >= 5) {
      clearInterval(timer);
    }
    console.log(a);
  };

  timer = setInterval(inner, 1000);
}

myFunc();

외부 객체인 window의 메서드에 전달할 콜백 함수 내부에서 지역 변수를 참조하는 경우 클로저가 발생합니다.

클로저와 메모리 관리

클로저는 개발자가 의도적으로 함수의 지역 변수를 메모리를 소모하도록 함으로써 발생합니다. 만약 메모리를 해제하고 싶다면 다음과 같이 식별자에 null이나 undefined를 할당하면 됩니다. 이렇게 하면 가비지 컬렉터가 메모리를 회수하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
let outer = function () {
  let a = 1;
  const inner = function () {
    return ++a;
  };

  return inner;
};

let outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
outer = null; // outer 식별자의 inner 함수 참조를 끊음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function myFunc() {
  let a = 0;
  let timer = null;

  let inner = function () {
    if (++a >= 5) {
      clearInterval(timer);
      inner = null; // inner 식별자의 함수 참조를 끊음.
    }
    console.log(a);
  };

  timer = setInterval(inner, 1000);
}

myFunc();

클로저 활용 예시

클로저가 활용되는 예시는 다음과 같습니다.

콜백 함수

다음과 같이 콜백 함수 내부에서 외부 변수를 참조할 때 클로저를 활용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Clusure Example</title>
  </head>
  <body>
    <p id="count">0</p>
    <button type="button" onclick="handleIncrease()">Increase</button>

    <script>
      const countElement = document.getElementById("count");
      let count = Number(countElement.innerText);

      const handleIncrease = function () {
        count++;
        countElement.innerText = count;
      };
    </script>
  </body>
</html>

클로저는 콜백 함수 내부에서 외부 변수를 참조할 때 활용할 수 있습니다.

정보 은닉 (Information Hiding)

정보 은닉(Information Hiding)이란 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 하는 개념입니다. 자바스크립트에서 클로저를 활용하면 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한을 부여할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const counter = function () {
  let count = 0;

  const increase = function () {
    console.log(++count);
  };

  const decrease = function () {
    console.log(--count);
  };

  return { increase, decrease };
};

const myCounter = counter();
myCounter.increase(); // 1
myCounter.increase(); // 2
myCounter.decrease(); // 1

위의 Counter는 increase, decrease 내부 함수로만 값을 변경할 수 있습니다. 즉, count = 10과 같이 count 값을 직접적으로 변경하는 행위를 방지할 수 있습니다.

부분 적용 함수 (Partially Applied Function)

부분 적용 함수(Partially Applied Function)이란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n - m)개의 인자를 넘겨서 원래 함수의 실행 결과를 얻을 수 있는 함수입니다. 아래 코드는 bind 메서드를 활용하여 인자 5개를 미리 적용한 새로운 함수를 만드는 예시입니다.

1
2
3
4
5
6
7
8
9
10
11
12
const add = function () {
  let result = 0;

  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }

  return result;
};

const addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55

커링 함수 (Currying Function)

커링 함수(Currying Function)란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말합니다. 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 합니다.

1
2
3
4
5
6
7
8
9
10
11
const curry3 = function (func) {
  return function (a) {
    return function (b) {
      return func(a, b);
    };
  };
};

const getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25

인자가 많아지는 경우 가독성이 떨어진다는 단점이 있습니다. 이 문제는 ES6의 화살표 함수를 사용해서 해결할 수 있습니다.

1
const curry3 = (func) => (a) => (b) => func(a, b);

참고 자료

This post is licensed under CC BY 4.0 by the author.