3 분 소요

closure 란?

  • 클로저(closure)는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.
  • 의미가 난해해서 예를 들어 익혀보자.
function outerFunc() {
  var x = 10;
  var innerFunc = function () {
    console.log(x);
  };
  innerFunc();
}

outerFunc(); // 10
  • 함수 outerFunc 내에서 내부함수 innderFunc가 선언되고 호출되었다. 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFucn의 내부에서 선언되었기 때문이다.
  • 동작 원리를 보자면,
    1. innerFunc 함수 스코프(함수 자신의 스코프를 가리키는 활성 객체)내에서 변수 x를 검색한다. 검색이 실패하였다.
    2. innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프(함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체)에서 변수 x를 검색한다. 검색이 성공하였다.
function outerFunc() {
  var x = 10;
  var innerFunc = function () {
    console.log(x);
  };
  return innerFunc;
}

/**
 *  함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
 *  그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
 */
var inner = outerFunc();
inner(); // 10
  • 함수 outerFunc는 내부함수 innerFunc를 반환하고 생을 마감했다. 즉, 함수 outerFunc는 실행된 이후 콜스택에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법이 없어 보인다.
  • 하지만 실행 결과는 10이다. 함수 outerFunc의 지역변수 x가 다시 부활이라도 한 듯이 동작하고 있다.
  • 이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데, 이러한 함수를 클로저 라고 부른다.

  • 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근 할 수 있는 함수를 말한다.
  • 간략하게 말하면 클로저는 자신이 생성될 때의 환경을 기억하는 함수이다.

  • 클로저에 의해 참조되는 외부함수의 변수 즉 outerFunc 함수의 변수 x를 자유변수라고 부른다.

클로저의 활용

상태 유지

  • 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지 하는 것이다.
<!DOCTYPE html>
<html>
  <body>
    <button class="toggle">toggle</button>
    <div
      class="box"
      style="width: 100px; height: 100px; background: red;"
    ></div>

    <script>
      var box = document.querySelector(".box");
      var toggleBtn = document.querySelector(".toggle");

      var toggle = (function () {
        var isShow = false;

        // ① 클로저를 반환
        return function () {
          box.style.display = isShow ? "block" : "none";
          // ③ 상태 변경
          isShow = !isShow;
        };
      })();

      // ② 이벤트 프로퍼티에 클로저를 할당
      toggleBtn.onclick = toggle;
    </script>
  </body>
</html>
  • 이 예제의 실행 과정을 살펴 보자면

    1. 즉시실행함수는 함수를 반환하고 즉시 소멸한다. 즉시실행함수가 반환한 함수는 자신이 생성됐을 때의 렉시컬 환경(Lexical environment)에 속한 변수 isShow를 기억하는 클로저다. 클로저가 기억하는 변수 isShow는 box 요소의 표시 상태를 나타낸다.

    2. 클로저를 이벤트 핸들러로서 이벤트 프로퍼티에 할당했다. 이벤트 프로퍼티에서 이벤트 핸들러인 클로저를 제거하지 않는 한 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않는다. 다시 말해 현재 상태를 기억한다.

    3. 버튼을 클릭하면 이벤트 프로퍼티에 할당한 이벤트 핸들러인 클로저가 호출된다. 이때 .box 요소의 표시 상태를 나타내는 변수 isShow의 값이 변경된다. 변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하며 자신의 변경된 최신 상태를 게속해서 유지한다.

  • 이 경우 클로저는 현재 상태를 기억하고 이 상태가 변경되어도 최신 상태를 유지해야 하는 상황에 매우 용이하다.

전역 변수의 사용 억제

  • 버튼이 클릭될 때마다 클릭한 횟수가 누적되어 화면에 표시되는 카운터를 만들어 보자.

(전역변수를 사용한 counting)

<!DOCTYPE html>
<html>
  <body>
    <p>전역 변수를 사용한 Counting</p>
    <button id="inclease">+</button>
    <p id="count">0</p>
    <script>
      var incleaseBtn = document.getElementById("inclease");
      var count = document.getElementById("count");

      // 카운트 상태를 유지하기 위한 전역 변수
      var counter = 0;

      function increase() {
        return ++counter;
      }

      incleaseBtn.onclick = function () {
        count.innerHTML = increase();
      };
    </script>
  </body>
</html>
  • 위의 코드는 잘 작동을 하지만 전역변수 counter가 0일 경우에만 정상적으로 작동이 될 것이다. 만약 전역변수의 값이 변경이 되었다면 다르게 작동을 할 것이다. 따라서 이 코드는 않좋은 코드이다.

(지역 변수를 이용한 counting)

<!DOCTYPE html>
<html>
  <body>
    <p>지역 변수를 사용한 Counting</p>
    <button id="inclease">+</button>
    <p id="count">0</p>
    <script>
      var incleaseBtn = document.getElementById("inclease");
      var count = document.getElementById("count");

      function increase() {
        // 카운트 상태를 유지하기 위한 지역 변수
        var counter = 0;
        return ++counter;
      }

      incleaseBtn.onclick = function () {
        count.innerHTML = increase();
      };
    </script>
  </body>
</html>
  • 전역변수를 지역변수로 변경하여 의도치 않은 상태 변경은 방지 했지만, increase 함수가 호출될 때 지역변수 counter를 0으로 초기화하기 때문에 언제나 1이 표시가 된다. 다시 말해 변경된 이전 상태를 기억하지 못한다.

(클로저를 사용한 counting)

<!DOCTYPE html>
<html>
  <body>
    <p>클로저를 사용한 Counting</p>
    <button id="inclease">+</button>
    <p id="count">0</p>
    <script>
      var incleaseBtn = document.getElementById("inclease");
      var count = document.getElementById("count");

      var increase = (function () {
        // 카운트 상태를 유지하기 위한 자유 변수
        var counter = 0;
        // 클로저를 반환
        return function () {
          return ++counter;
        };
      })();

      incleaseBtn.onclick = function () {
        count.innerHTML = increase();
      };
    </script>
  </body>
</html>
  • 스크립트가 실행되면 즉시실행함수가 호출되고 변수 increase에는 함수 function() { retrurn ++counter; } 가 할당된다. 이 함수는 클로저 함수로 지역변수 counter를 기억한다. 따라서 변수 counter에 접근할 수 있고 변수 counter는 자신을 참조하는 함수가 소멸될 때 까지 유지된다.
  • 변수 counter는 외부에서 직접 접근 할 수 없으므로, 의도하지 않은 변경을 걱정 할 필요 없이 안정적인 프로그래밍이 가능하다.

댓글남기기