코딩쌀롱

[코어 자바스크립트] 5-1,2. 클로저의 의미, 원리 이해와 메모리 관리 본문

[코어 자바스크립트] 5-1,2. 클로저의 의미, 원리 이해와 메모리 관리

이브✱ 2020. 12. 23. 16:49

5-1 클로저의 의미 및 원리 이해

const outer = function () {
   let a = 1;
   const inner = function() {
      console.log(++a);
   };
   inner();
};

outer();

(1) inner 함수 내부에서는 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못하므로 outerEnvironmentReference에 지정된 상위 컨텍스트인 outer의 LexicalEnvironment(L.E)에 접근해서 다시 a를 찾는다.

(2) 그리고 2가 출력된다.

(3) outer 함수의 실행 컨텍스트가 종료되면 L.E에 저장된 식별자들(a, inner)에 대한 참조를 지운다. 그러면 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수집 대상이 될 것이다.

 

outer의 실행 컨텍스트가 종료된 후에도 inner 함수를 호출할 수 있게 만들어 보자.

const outer = function () {
  let a = 1;
  const inner = function () {
    return ++a;
  };
  return inner;        // 함수를 리턴
};

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

 outer 함수에서 inner 함수 자체를 반환했다. outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 outer의 실행 결과인 inner 함수를 참조하게 된다. ( outer2 == outer(); == inner ) 이후 outer2를 호출하면 inner가 실행된다.

 

inner 함수 environmentRecord 없음
outerEnvironmentReference inner 함수가 선언된 위치의 L.E 참조복사
== outer 함수의 L.E ( outer함수의 변수에 접근 가능 )

 

 inner 함수의 실행 시점에는 outer 함수는 이미 실행이 종료된 상태인데 outer 함수의 L.E에 어떻게 접근 가능할까?

 가비지 컬렉터의 동작 방식 때문이다. 어떤 값을 참조하는 변수가 하나라도 있으면 수집 대상에 포함시키지 않는다.

 외부함수인 outer의 실행이 종료되더라도 내부 함수인 inner는 언젠가 outer2를 실행함으로써 호출될 가능성이 열린 것이다. inner 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer의 L.E를 필요로 할 것이므로 수집대상에서 제외된다. 그 덕에 inner가 변수에 접근할 수 있는 것이다. 

 

 클로저는 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이다. 함수의 실행 컨텍스트가 종료된 후에도 LexicalEnvironment가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 지역변수를 참조하는 내부함수가 외부로 전달되는 경우가 유일하다. 즉, 외부 함수의 LexicalEnvironment가 가비지 컬렉팅되지 않는 현상을 말한다.

 

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

 

 '외부로 전달'이 곧 return만을 의미하는 것은 아니다. return 없이도 클로저가 발생하는 다양한 경우가 있다.

// (1) setInterval/setTimeout

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

별도의 외부객체인 window의 메서드(setTimeout, setInterval)에 전달할 콜백 함수 내부에서 지역변수를 참조

 → 1초마다 콜백함수인 inner(내부함수)가 실행. 외부함수의 지역변수(a)를 참조

 

// (2) eventListener

(function () {
   let count = 0;
   const button = document.createElement('button');
   button.innerText = 'click';
   
   button.addEventListener('click', function () {
      console.log(++count, 'times clicked');
   });
   
   document.body.appendChild(button);
})();

별도의 외부객체인 DOM의 메서드(addEventListener)에 등록할 handler 함수 내부에서 지역변수를 참조

 → 클릭 이벤트가 발생할 때마다 콜백함수(내부함수) 실행, 외부함수의 지역변수(count)를 참조

 

5-2 클로저와 메모리 관리

메모리 소모는 클로저의 본질적인 특성. 이러한 특성을 정확히 이해하고 잘 활용해야 한다. 개발자의 의도대로 설계한 '메모리 소모'에 대한 관리법을 잘 파악해서 적용하면 '메모리 누수'가 아니라고 할 수 있다.

 

클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생한다. 그 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해주면 된다. 참조 카운트를 0으로 만들면 언젠가는 GC가 수거해갈 것이고, 이 때 소모됐던 메모리가 회수될 것이다. → 식별자에 기본형 데이터를 할당(null, undefined)

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

const outer = (function () {
   let a = 1;
   const inner = function () {
      return ++a;
   };
   return inner;
})();

console.log(outer());
console.log(outer());
outer = null;             // outer 식별자의 inner 함수 참조를 끊음
// (2) setInterval에 의한 클로저의 메모리 해제

(function () {
   let a = 0;
   let intervalId = null
   const inner = function () {
      if (++a >= 10) {
         clearInterval(intervalId);
         inner = null;         // inner 식별자의 함수 참조를 끊음
      }
      console.log(a);
   };
   intervalId = setInterval(inner, 1000);
})();
// (3) eventListener에 의한 클로저의 메모리 해제

(function () {
   let count = 0;
   const button = document.createElement('button');
   button.innerText = 'click';
   
   let 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);
})();

 

 

 


정재남 작가님의〔 코어 자바스크립트 〕책을 공부하며 정리했습니다.

Comments