FrontEnd/JavaScript

Modern JavaScript 클로저

is..cy 2023. 1. 29. 13:26

 


 

1. 클로저란 

 

 

 

  • excution context 에서 필요한 개념으로, 함수 선언시 렉시컬 환경 (어디에 함수를 선언하였는가, 외부/내부)의 조합을 의미
  • excution context 관점에서 innerFunc이 호출되면 context에 stack이 쌓이고 Variable Object와 Scope chain, this에 바인딩할 객체가 결정된다.
  • outerFunc의 Activation Object, 함수자신을 순차적으로 바인딩하며 scope chain이 바인딩한 객체가 렉시컬 스코프 범주의 개념이다.
  • 반환된 내부 함수가 자신이 선언되었을 때의 환경 (Lexical environment)인 스코프를 기억하여 자신이 선언되었을 때의 환경(스코프)밖에서 호출되어도 Lexical environment를 기억하는 함수 -> 스코프체인을 통해 참조
  • 상태 변경이나 가변데이터를 피하고 불변성을 지향하는 함수형 프로그래밍으로 부수효과를 최대한 억제하여 오류를 피함 (안정성)
  • 메모리 차원에서는 손해보는 환경

 

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  innerFunc();
}

outerFunc(); // 10

 

* innerFunc 함수가 변수x 검색  --> 실패 --> outerFunc (활성객체) 에서 변수 x (자유변수) 검색 --> 성공 --> 반환 (콜스택, 실행 컨택스트에서 outerFunc 소멸)

- 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되어도 외부함수의 지역 변수에 접근할 수 있다.

 

 


 

2. 클로저 활용

 

  • 상태유지 : 현상태를 기억하고 변경된 상태 유지

        - isShow (Lexical environment의 변수, 생성시 기억) : 클로저가 기억하는 변수

        - 이벤트 핸들러인 클로저를 제거하지 않는한 isShow는 소멸하지 않는다. (현상태 기억)

        - 버튼 클릭시 이벤트핸들러 클로저 호출

        - isShow값 변경 및 최신상태 유지 

 

<!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>

 

  • 전역 변수 사용 억제

        - counter (전역변수) : 전역변수로 언제든 접근 및 변경 가능

        - increase (클로저) : 의도치 않게 값이 변경 되는 것을 방지 (자신이 생성됐을 때의 환경을 기억)

<!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>

 

  • 정보의 은닉

        - public property : counter가 this에 바인딩된 프로퍼티라면 Counter는 외부에서 접근 가능

        - private 하게 사용 : 아래 예시는 Counter 내에서 선언된 변수 counter는 외부에서 접근 불가 

 

function Counter() {
  // 카운트를 유지하기 위한 자유 변수
  var counter = 0;

  // 클로저
  this.increase = function () {
    return ++counter;
  };

  // 클로저
  this.decrease = function () {
    return --counter;
  };
}

const counter = new Counter();

console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0