JavaScript - 클로저

euNung·2022년 6월 27일
0

JavaScript

목록 보기
5/7
  • 클로저
    : 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
    => 지역변수를 참조하는 내부함수가 외부로 전달된 경우

    var outer = function () {
     var a = 1;
     var inner = function () {
       return ++a;
     };
     return inner;				// inner 함수의 실행 결과가 아닌 inner 함수 자체를 반환
    };
    
    var outer2 = outer();
    console.log(outer2());		// 2
    console.log(outer2());		// 3

    => outer 함수의 실행 컨텍스트가 종료 될 때 outer2의 변수는 outer의 실행 결과인 inner 함수를 참조하게 됨
    => inner 함수의 실행 컨텍스트의 envrionmentRecord에는 수집할 정보가 없으므로 스코프 체이닝에 따라 outerEnvionmentReference(= outer 함수의 LexicalEnvrionment)에 담긴 변수 a에 접근하여 1만큼 증가시킨 값을 반환 후 inner 함수의 실행 컨텍스트가 종료됨

    💙 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않음
    => outer의 LexicalEnvrionment에 속하는 변수 중 변수 a가 가비지 컬렉터의 수집 대상에서 제외됨

    • 예시)

      // setInterval
      (function () {
        var a = 0;
        var intervalId = null;
        var inner = function () {
          if (++a >= 10) {
            clearInterval(intervalId);
          }
          console.log(a);
        };
        intervalId = setInterval(inner, 1000);				// 콜백 함수 내부에서 지역변수 참조 
      })();
      
      // eventListener
      (function () {
        var count = 0;
        var button = document.createElement('button');
        button.innerText = 'click';
        button.addEventListener('click', function () {
          console.log(++count, 'times clicked');				// handler 함수 내부에서 지역변수 참조
        });
        document.body.appendChild(button);
      })();
      

  • 메모리 관리
    클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생

    • 해결방법
      - 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해줌
      - 참조 카운트를 0으로 만듦 => 언젠가 GC가 수거 => 소모됐던 메모리 회수

    • 참조 카운트를 0으로 만드는 방법
      : 식별자에 참조형이 아닌 기본형(보통 null이나 undefined) 할당

      // (1) return에 의한 클로저의 메모리 해제
      var outer = function () {
      	var a = 1;
      	var inner = function () {
         	return ++a;
      	};
         return inner;				
      };
      
      var outer2 = outer();
      console.log(outer2());
      console.log(outer2());
      
      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);
       })();
       

  • 클로저 활용

    • 고차 함수

      var fruits = ['apple', 'banana', 'peach'];
       var $ul = document.createElement('ul');
      
       var alertFruitBuilder = function (fruit) {
         return function () {
           alert('your choice is ' + fruit);
         }
       };
      	
       fruits.forEach(function (fruit) {
         var $li = document.createElement('li');
         $li.innerText = fruit;
         $li.addEventListener('click', alertFruitBuilder(fruit));
         $ul.appendChild($li);
       });
       document.body.appendChild($ul);
       alertFruit(fruits[1]);
      
    • 정보 은닉
      : 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 함

      • 클로저를 이용하여 함수 차원에서 public한 값과 private한 값을 구분하는 것이 가능

         var outer = function () {
           var a = 1;						
           var inner = function () {
             return ++ a;
           };
           return inner;					
         };
         var outer2 = outer();
         console.log(outer2());
         console.log(outer2());
        
         // outer 함수를 종료할 때 inner 함수를 반환함으로써 
         // outer 함수의 지역 변수인 a의 값을 외부에서도 읽을 수 있음 => public

        => 외부에 제공하고자 하는 정보들을 모아서 return하고(=> public), 내부에서만 사용할 정보들은 return하지 않는 것(=> private)으로 접근 권한 제어 가능

    • 부분 적용 함수
      : n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비소로 언래 함수의 실행 결과를 얻을 수 있게끔 하는 함수

      • 디바운스
        : 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것
        => 프론트엔드 성능 최적화에 큰 도움을 줌

        var debounce = function (eventName, func, wait) {
          var timeoutId = null;
          return function (event) {
            var self = this;
            console.log(eventName, 'event 발생');
            clearTimeout(timeoutId);
            timeoutId = setTimeout(func.bind(self, event), wait)l
          };
        };
        
        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));
        
        // 클로저 처리되는 변수: eventName, func, wait, timeoutId
    • 커링 함수
      : 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것
      : 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 함

       var getInformation = function (baseUrl) {
         return function (path) {
           return function (id) {
             return fetch(baseUrl + path + '/' + id);
           };
         };
       };
      
       // ES6
       var getInformation = baseUrl => id => fetch(baseUrl + path + '/' + id);
      
       var imageUrl = 'http://imageAddress.com/';
      
       var getImage = getInfromation(imageUrl);	// http://imageAddress.com
      
       var getEmotion = getImage('emotion');      // http://imageAddress.com/emotion
       var getIcon = getImage('icon');			// http://imageAddress.com/icon
      
       var emotion = getEmotion(100);    			// http://imageAddress.com/emotion/100
       var icon = getIcon(205);				   	// http://imageAddress.com/icon/205
profile
프론트엔드 개발자

0개의 댓글