코어 자바스크립트(클로저)

L·2022년 12월 9일
0

코어자바스크립트

목록 보기
5/6
post-thumbnail

1. 클로저의 의미 및 원리 이해

  • 사전적 의미는 폐쇄,닫힘,완결성.

  • 함수형 프로그래밍 언어에서 나타나는 보편적인 현상.

  • MDN에서 정의하는 클로저

    A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
    클로저는 주변 상태(the lexical environment)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다. 즉, 클로저는 내부함수로부터 외부함수 scope에 대한 제어(?)를 제공한다. JavaScript에서 클로저는 함수가 생성될 때마다 생성됩니다.

  • 예제소스

var outer = function(){
  var a = 1;
  var inner = function(){
    return ++a;
  }
  return inner;
}
var outer2 = outer();
console.log(outer2()); //2
console.log(outer2()); //3
  • outer2변수는 inner 함수를 호출한 결과값을 가지는것이 아닌, inner 함수 자체를 가짐.(= outer2 함수를 호출할때마다 inner 함수의 실행컨텍스트를 참조.)
  • outer2 변수를 호출할때마다, inner함수의 실행컨텍스트를 참조하는데, environmentRecord에는 a 식별자가 없기때문에 outerEnvironmentReference(=outer함수의 lexical environment)를 참조.
  • inner함수의 outerEnvironmentReferencea변수가 있으니, a 값을 1 증가시킨 결과를 리턴.

결과에서 보듯 outer실행 컨텍스트가 종료가 되었지만, outer2 함수를 호출 할때마다, 변수 a는 전역컨텍스트가 종료될때까지 끝까지 살아 남아있음.

왜 함수는 종료 상태인데 외부함수의 LexicalEnvironment에 접근이 가능할까?

가비지 콜렉터(GC)는 값을 참조하는 변수가 하나라도 있다면, 수집 대상에 포함되지 않음.
(garbage collector란? 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능)

  • outer2 함수를 호출할때마다 inner 실행컨텐스트가 활성화 되면서 a변수를 참조하고 있기 때문에 outerEnvironmentReference(=outer함수의 lexical Environment)를 필요로 하는 상황이라, GC수집 대상에서 제외.

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

  • 함수를 외부로 전달하지않고도 클로저가 발생할수 있음.
(function(){
  var a = 10;
  var intervalID = null;
  var callback = function(){
    if(++a >= 20){
		clearInterval(intervalID);
    }
    console.log(a);
  }
  intervalID = setInterval(callback,1000);
})()
  • setInterval의 콜백함수 내부에서 callback함수의 지역변수에 a변수와 intervalID가 없기 때문에 outerEnvironmentReference가 즉시 실행함수를 참조하는 현상이 발생하기 때문에 클로저가 발생.

2.클로저와 메모리 관리

클로저가 발생한 함수를 소모하지않게 변수를 null or undefined로 할당.

3. 클로저의 활용 사례

1.콜백 함수 내부에서 외부 데이터를 사용할때

const fruits = ['apple','banana','grape'];
const $ul = document.createElement('ul');
const clickEventHandler = function(fruit){ 
  return function(){
    alert(`My Choose is ${fruit}`);
  }
};
fruits.forEach(fruit=>{
  const $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click',clickEventHandler(fruit));
  $ul.appendChild($li);
})
document.body.appendChild($ul);

fruits 배열 만큼 li태그를 생성한다음 click 이벤트를 추가했다. click 이벤트의 콜백 함수인 clickEventHandlerfruit값을 인자로 전달하고 익명함수를 리턴한다. 이벤트가 호출이 될 때 반환된 함수안에는 fruit지역변수가 없기때문에,outerEnvironmentReferenceclickEventHandlerlexical Environment를 참조하여 해당값을 읽어서 실행결과를 alert로 띄워준다. 이 때 clickEventHandler의 익명함수에는 클로저가 존재.

2.접근 권한 제어(정보 은닉, private 흉내)

  • javascript에는 java의 public,private같은 접근 제한이 따로 없음. 하지만 클로저를 이용해 흉내내는것은 가능.
function counter(){
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
}
const _counter = counter();
console.log(_counter.increment()); // 1
console.log(_counter.increment()); // 2
console.log(_counter.decrement()); // 1
console.log(_counter.value()); // 1

counter함수는 해당 값을 계산해주는 객체를 반환하는데,객체 안에 있는 메서드를 호출할때마다, 각 메서드에는 각각 내부함수와 지역변수가 없기때문에, outerEnvironmentReferencecounter함수의 lexicalEnvironment를 참고하여, 해당값과 함수를 읽어서 실행하기 때문에 클로저가 발생. 이 때 changeBy,privateCounter는 바로 접근해서 사용할수 없기때문에 private접근자 처럼 된다.

3.부분 적용 함수

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

const debounce = function (eventName, func, wait) {
  var timerId = null;

  return function (event) {
    var self = this;
    console.log(`${eventName} Trigger!!`);
    clearTimeout(timerId);
    timerId = setTimeout(func.bind(self, event), wait);
  };
};
const keyupHandler = ({target}) => {
  console.log(`keyup Event Value is ${target.value}`);
};
const $input = document.createElement('input');
$input.addEventListener("keyup", debounce("keyup", keyupHandler, 1000));

1 . debounce 함수는 eventName 과 실행할 func와 이벤트 대기시간(wait)를 받음. 내부 함수에서는 eventName을 로그에서 출력하고 기존 타이머를 취소하고 setTimeout가 생성되어 wait 이후에 func 함수 호출.
2 .input 태그 생성하고 keyup 이벤트를 부여. 이벤트 콜백함수는 debounce 함수가 리턴하는 내부함수가 된다.
3 .keyup이벤트 발생시, 내부함수에서는 eventName,func,wait,timerId는 내부변수에 없기 때문에 outerEnvironmentReferencedebounce함수의 lexicalEnvironment를 참고한다.
따라서, eventName,func,wait,timerId는 클로저로 처리되는 변수.

⚙️실무에서 대표적으로 사용되는 debounce기법이 대표적인 예.

debounce는 짧은 시간동안 동일한 이벤트가 발생시, 발생한 이벤트를 전부 처리하는것이 아닌 처음 또는 마지막 이벤트에 대해 한번만 처리 하는 기법.
예를 들어, 내용을 입력할때마다, 호출 건수에 제한이 있는 API를 호출할때 마지막에 입력된 단어만을 가지고 API를 호출하면 되기때문에 유용.

4. 커링 함수

커링함수란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성 한 것. 마지막 인자가 전달되기 전까지는 원본 함수가 실행 되지않는것이 특징.

const curryingFunc = function(func){
  return function (param1){
    return function (param1_1){
      return func(param1,param1_1);
    }
  }
}
//ES6
// const curryingFunc = func=>param1=>param1_1=>func(param1,param1_1);
const customMax = curryingFunc(Math.max)(10);
console.log(customMax(100)); // 100
console.log(customMax(50)); // 50
const customMin = curryingFunc(Math.min)(10);
console.log(customMin(8)); // 8
console.log(customMin(25)); // 10
  • 이전 단계에서 받는 인자들을 참조하기 때문에 GC되지 않고, 메모리에 쌓였다가 마지막 호출시 내부 함수내에 실행 컨텍스트가 종료된후 GC가 수거해감.
  • 대표적인 예
    • 지연실행(lazy execution, 원하는 시점까지 지연시켰다가 필요한 상황에 실행)
    • Redux의 미들웨어. 공통적으로 store,next,action을 순서대로 받으며, store는 변하지않는 속성, nextdispatch와 같은의미를 가지며 변하지않는 속성인건 똑같지만, action는 매번 달라지기 때문에, loggerstore,next는 미리 넘겨서 함수로 저장시킨다음, action만 받아서 처리할수가 있음.
      			//redux middleware logger
      			const logger = store=>next=>action=>{
       console.log(`dispatch ${action}`);
       console.log(`next state ${store.getState()}`);
       return next(action);
      }

정리

  • 클로저란 어떤 함수에서 선언한 변수를 참고하는 내부함수를 리턴 시 해당 함수를 호출 시 실행 컨텍스트가 종료된 후에도 변수가 좀비처럼 살아 남는 현상을 말함.
  • 클로저는 내부함수를 외부로 전달하는경우 외에도 콜백함수에서 전달하는 경우에도 발생
  • 클로저는 내부함수가 외부함수에서 선언한 함수를 참조시 메모리를 계속 차지가히때문에, 사용하지않을경우는 관리가 필요.

참고

0개의 댓글