본문 바로가기
웹(web)/프론트엔드-javascript

[Closure]

by 바코94 2020. 6. 3.

버튼을 클릭했을 때 body의 font-size를 바꾸는데 버튼 별로 12px, 14px, 16px을 위한 버튼을 만들고 싶으면 어떻게 할까?

 

답은 클로저를 사용하면 편리하다.

 

function makeSizer(size) {

  return function() {

    document.body.style.fontSize = size + 'px';

  };

}

var size12 = makeSizer(12);

var size14 = makeSizer(14);

var size16 = makeSizer(16);

 

위 코드 덕에 size12()는 document.body.style.fontSize= 12px; 를 실행한 것과 같다.

 

html 상에서 a 태그를 세개 만들고

<a href="#" id="size-12">12</a>

<a href="#" id="size-14">14</a>

<a href="#" id="size-16">16</a>

 

스크립트로 핸들러에 등록해주면 

document.getElementById('size-12').onclick = size12;

document.getElementById('size-14').onclick = size14;

document.getElementById('size-16').onclick = size16;

 

a 태그를 클릭할 때 마다 body의 font-size가 바뀐다.

 

여기서 클로저는 리턴하는 함수인 function(){ document~~ 'px';) 이다.

클로저는 어떻게 작동하는 것일까? 결론은 클로저는 클로저를 리턴하는 시점의 정보들을 사용하게 된다. 즉, makeSizer(12)를 호출하고 return 하기 전 까지의 정보를 사용하는 것이다. makeSizer(12)를 호출하면 makeSizer context에 size=12 정보가 저장된다. 클로저를 리턴할 시점에 size=12였기 때문에 앞으로 클로저는 size=12 정보를 계속 사용한다. 이 정보는 makeSizer를 호출한다고 해도 바뀌지 않고 계속 유지된다. 

execution context에서 호출이 끝나면 context가 사라진다고 했는데 클로저를 사용하게 되면 사라지지 않고 해당 클로저를 위해 남아있게 된다.

 

 

객체의 인터페이스를 클로저를 사용하여 구현해보자.

var counter = (function() {

  var privateCounter = 0;

  function changeBy(val) { privateCounter += val; }

 

  return {

    increment: function() { changeBy(1); },

    decrement: function() { changeBy(-1); },

    value: function() { return privateCounter; }

  };

})();

 

복잡해 보이지만 구조는 간단하다. 이름이 counter인 변수를 만들었다. counter 변수를 만들기 위해 익명 함수 하나를 실행한다. 익명함수는 속성 세 개가 있는 객체를 리턴한다. 세 개의 속성은 각각 클로저이다. 왜냐하면 inner function이며 리턴하는 부분에서 사용되기 때문이다. 따라서, 클로저들은 리턴될 때의 정보를 사용한다. 이 때, 클로저 세 개는 같은 정보를 사용하게 된다. 즉, privateCounter, changBy 객체 두개를 공유한다.

 

앞으로 counter.increment를 하면 changeBy를 통해 privateCounter 값을 1 증가시킨다. privateCounter는 프로그램에서 유일한 값이 되었으며 counter 변수의 인터페이스인 increment, decrement를 통해 변경할 수 있고 value를 통해 값을 얻어올 수 있다.

 

여기서 헷갈릴 수 있는 부분은 클로저를 사용하면 스택관리 방식이 exucution context와 다른 것이다. 함수를 호출하고 실행이 끝나면 context가 스택에서 사라진다는 규칙이 적용이 안되기 때문이다. A()를 호출하면 A context가 Runtime Execution context 스택에 쌓인다. 원래라면 리턴이 되고 A context가 스택에서 없어진다. 

하지만, 리턴될 때 클로저가 있다면 A context를 이 클로저를 위해서 사용하기 위해서 남겨둔다. 이 순간 리턴할 때 사용한 유일한 클로저를 클로저1이라고 하고 이 때의 정보를 A context1이라고 하자. A context1은 오직 클로저1 만을 위한 것이다. 따라서, 이후 코드에서는 방금 생성된 스택인 A context1는 클로저1만이 사용할 수 있게 된다. 인자, 로컬 변수, 로컬함수 등이 모두 방금 리턴된 해당 클로저만을 위한 전용 객체들이 되는 것이다. 단, A Context1은 클로저1만을 위한 것이므로 Runtime Execution Context에서 지워진 

다음에 또 A()를 호출하면? 이것은 A context의 새로운 형태인 A context2가 되기 때문에 A context1과는 전혀 다르다. 붕어빵 틀에서 붕어빵을 찍어낼 때 완전히 똑같은 것은 존재할 수 없다. 

 

 

e =10
function sum(a){
  return function sum2(b){
    return function sum3(c) {
      return a + b + c+ e;
    }
  }
}

var level1 = sum(5);
var level2 = level1(5);
var level3 = level2(5);
console.log(level3);

level1 = sum(500);

console.log(level3);

 

위 예시의 인사이트는 클로저는 딱 리턴하는 그 순간의 정보들을 사용한다는 것이다. level3을 만들고 나서 level1을 아무리 바꿔도 level3은 바뀌지 않는다. 동적으로 클로저의 관련 정보들이 정해진다고 볼 수 있겠다.

 

참고 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures

'웹(web) > 프론트엔드-javascript' 카테고리의 다른 글

객체, 배열의 내장함수 정리  (0) 2020.06.27
javascript 추가 기능  (0) 2020.06.27
[Execution Context] Function call  (0) 2020.06.03
[Prototype] Prototype Chaining  (0) 2020.06.02
[Function] return value  (0) 2020.06.01