클로저, 프로토타입

베니·2022년 5월 2일
0

JS

목록 보기
24/24
post-thumbnail

클로저

클로저를 사용하려면 무조건 return을 사용해야 한다고 생각하고 있었다. 하지만 다음 코드를 보며 생각을 고쳤다.

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

별도의 외부 객체인 window의 메서드(setTiemout 또는 setInterval)에 전달할 콜백 함수 내부에서 지역변수를 참조한다.

// (2) eventListener
(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)에 등록할 handler 함수 내부에서 지역변수를 참조한다.

두 상황 모두 지역변수를 참조하는 내부함수를 외부에 전달했기 때문에 클로저이다.

그리고 클로저를 사용하고나서는 GC가 메모리를 수거하지 않기 때문에 직접 제거해주는 편이 좋다.

// (1) return에 의한 클로저의 메모리 해제
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에 의한 클로저의 메모리 해제
(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에 의한 클로저의 메모리 해제
(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);
})();

옛날 프로젝트를 하며 디바운스에 어떻게 인수를 넘겨줄까 고민을 한 적이 있었다. 그때에는 bind로 인수를 넘겨주었는데 클로저를 활용하는 방법도 있었다.

클로저를 활용한 디바운스 구현 예시

var debounce = function (eventName, func, wait) {
  var timeoutId = null;
  return function (event) {
    var self = this;
    console.log(eventName, 'event 발생');
    clearTimeout(timeoutId);
    timeotId = setTimeout(func.bind(self, event), wait);
  };
};

var moveHandler = function (e) {
  console.log('move event 처리');
};
var wheelHandler = function (e) {
  console.log('wheel event 처리');
};
document.body.addEventListener('mousemove', debounce('move', moveHandler, 500));
document.body.addEventListener('mousewheel', debounce('wheel', wheelHandler, 700));

프로토타입

prototype

각 생성자 함수는 모두 함수이기 때문에 Function 생성자 함수의 prototype과 연결된다. Function 생성자 함수 역시 함수이므로 다시 Function 생성자 함수의 prototype과 연결된다. 이런 식으로 __proto__ 의 constructor의 __proto__ 의 constructor ..를 재귀적으로 반복하는 루트를 따르면 끝없이 찾아갈 수 있어서 실제 메모리 상에서 데이터가 무한대의 구조 전체를 들고 있을 것이라고 생각했는데 사실은 구조 전체를 들고 있는 것이 아니고 사실 instance.constructor.contructor 이든 instance.constructor.contructor.constructor이든 결국 같은 Function 생성자 함수를 가리키므로 메모리가 낭비될 이유는 없다고 한다.

참고

코어 자바스크립트

profile
안녕하세요~

0개의 댓글