웹/JavaScript

JavaScript Closure

클라우드아실 2021. 1. 18. 00:35

JavaScript Closure

 : 클로저는 '어떤 함수에서 선언한 변수를 참조하는 내부 함수를 외부로 전달할 경우, 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상'을 의미합니다. 또 그렇게 사용된 함수를 클로저 함수라 합니다. 외부함수가 종료된 이후에도 내부함수가 외부함수에 접근할 수 있습니다. 내부 함수가 소멸될 때까지 외부함수는 소멸되지 않습니다. 렉시컬 스코핑과 함수와의 관계를 잘 봐야합니다.

function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name); // "Mozilla"
  }
  return displayName;
}

var myFunc = makeFunc();

myFunc(); // "Mozilla"

 

내가 이해한 클로저의 2가지 기준

 : 첫번째 기준은 중첩된 함수에서 내부 함수가 상위 스코프의 변수를 참조하고 있어야 합니다. 동일한 코드를 다시 한번 보면 diplsyName() 함수 내부의 name은 선언된 내용이 없어서 상위 함수 makeFunc() 의 name을 참조합니다. 

function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  /* return displayName;
}

var myFunc = makeFunc();

myFunc(); */

 

 : 두번째 기준은 외부 함수의 외부로 반환되어 외부 함수보다 더 오래 유지되는 경우로 한정합니다. return을 활용하는 방법도 있지만 콜백으로 전달하는 경우도 포함됩니다.

/* function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name);
  } */
  return displayName;
}

var myFunc = makeFunc();

myFunc(); 

 

 : 따라서 전체 코드는 아래의 순서대로 실행됩니다.

function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name); // "Mozilla"
  }
  return displayName;
}

var myFunc = makeFunc();

myFunc(); // "Mozilla"
  • 9번줄 myFunc는 makeFunc()을 실행한 리턴값 displayName을 변수에 담습니다.
  • 11번줄 myFunc()가 실행되면 name은 지역변수로 원래라면 실행시킬 수 없어야 합니다.
  • 4번줄 name은 Lexical scoping으로 상위의 변수 name을 참조하게되고 'Mozilla'를 할당합니다.
  • makeFunc()함수 선언 당시 환경을 그대로 유지하게 되어 상위변수를 참조하는 것이 유지됩니다.
  • 결과값은 name인 "Mozilla"가 반환됩니다. 여기서 displayName은 클로저 함수가 됩니다.

 

클로저 활용 예제

 

커링

 : 함수 하나가 n개의 함수를 만들어 각각 인자를 순차적으로 받아 호출될 수 있게 체인 형태로 구성한 것입니다. 커링은 마지막 인자가 전달되지 전까지는 원본 함수가 실행되지 않고 대기합니다. 

function adder(x) {
  return function(y){
    return x + y;
  }
}
adder(2)(3); // 5
  • adder 함수를 호출하면서 순서대로 2, 3이 각각 x와 y로 할당됨

 

 : 화살표 함수를 사용할 경우 커링 함수의 흐름이 한눈에 파악됩니다.

let maxValue = function (func) {
  return function(a){
    return function(b){
      return function(c){
        return function(d){
          return function(e){
            return func(a, b, c, d, e);
          }
        }
      }
    }
  }
}
let getMax = maxValue(Math.max);
console.log(getMax(1)(2)(3)(4)(5)); // 5

let maxValue = func => a => b => c => d => e => func(a, b, c, d, e);

let getMax = maxValue(Math.max);
console.log(getMax(1)(2)(3)(4)(5)); // 5

 

 : x의 값을 고정해 놓고 재사용할 수 있습니다. 커링을 변수에 할당하고 다시 그 변수를 인자를 넣어 호출하면 됩니다. 아래에는 동일한 내용을 화살표 함수를 이용해서 나타냈습니다.

function adder(x) {
  return function(y){
    return x + y;
  }
}
let add100 = adder(100);

add100(2); // 102


adder => x => y => x + y;

let add100 = adder(100);

add100(2); // 102

 

 : 외부 함수의 변수를 내부함수가 템플릿 함수처럼 재사용 할 수도 있습니다.

function htmlMaker(tag) { // 1번 여기로
  let startTag = '<' + tag + '>';
  let endTag = '</' + tag + '>';
  return function(content) { // 2번 여기로
    return startTag + content + endTag;
  }
}

let divMaker = htmlMaker('div'); // 1번

divMaker('안녕하세요') // 2번
// <div>안녕하세요</div>
  • htmlMaker 함수를 통해 div가 고정 재사용되고, divMaker를 호출시 인자가 content로 function에 할당됨

 

클로저 모듈 패턴

 : 변수를 스코프 안쪽에 가두어 함수 밖으로 노출시키지 않고 모듈 패턴처럼 사용할 수 있습니다. 아래 예시의 return은 객체의 형태를 띄고 있습니다.

function makeCounter() {
  let privateCounter = 0;
  
  return {
    increment: function() {
      privateCounter ++
    },
    
    decrement: function() {
      privateCounter --
    },
    
    getValue: function() {
      return privateCounter;
    }
  }
}

let counter1 = makeCounter();

counter1.increment()
counter1.increment() // 2

let counter2 = makeCounter();
counter2.increment()
counter2.increment()
counter2.increment()
counter2.increment()
counter2.increment() // 5

counter1.getValue // 2
counter2.getValue // 5

  • 만약 privateCounter가 임의조작 가능하면 연산 결과가 달라질 수 있음.
  • makeCounter 함수 내에 return에 함수를 두어 increment나 decrement, getValue에 접근하지 못하게 함.
  • let count2 = makeCounter (); 형태로 새로운 변수에 할당하면 기존 함수와 별도의 새로운 함수로 재사용 가능.
  • 함수 내에 let privateCounter 라는 지역 변수가 있기 때문에 두 변수의 연산결과가 달라지게 됨.

 

정리

  • closure는 어떤 함수에서 선언한 변수를 참조하는 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상
  • 중첩된 함수에서 내부 함수가 상위 스코프의 변수를 참조하고 있어야 함.
  • 외부 함수보다 더 오래 유지되는 경우로 한정함.
  • 커링. n개의 함수를 하나의 함수가 받아 호출
  • 클로저 모듈 패턴. 스코프 안쪽에 객체 형태로 return

 

참고

모던 자바스크립트 튜토리얼 - 변수의 유효범위와 클로저

생활코딩 - 클로저

MDN 클로저

PoiemaWeb - 클로저

코어 자바스크립트 - 정재남 저 위키북스

HEROPY Tech - Closure(함수 클로저)