[js] 클로저 (Closure)

비트·2023년 4월 30일
0

JavaScript

목록 보기
7/22
post-thumbnail

클로저 (Closure)

  • 클로저는 함수와 함수가 선언된 어휘적 환경의 조합
    • 함수와 그 함수가 접근할 수 있는 변수의 조합
    • 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해하자

어휘적 범위 지정(Lexical scoping)

  • function init() {
          let name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
          function displayName() { // displayName() 은 내부 함수이며, 클로저다.
            alert(name); // 부모 함수에서 선언된 변수를 사용한다.
          }
          displayName();
        }
        init();
  • init()은 지역 변수 name과 함수 displayName()을 생성

  • displayName()init() 안에 정의된 내부 함수이며 init() 함수 본문에서만 사용할 수 있다.

    • 여기서 주의할 점은 displayName() 내부엔 자신만의 지역 변수가 없다는 점이다.

      • displayName() 함수 내에서 선언된 변수가 없다.
    • 그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName() 역시 부모 함수 init()에서 선언된 변수 name에 접근할 수 있다.

    • 만약 displayName()가 자신만의 name변수를 가지고 있었다면, name대신 this.name을 사용했을 것이다.


  • 위 코드를 실행하면 displayName() 함수 내의 alert()문이 부모 함수에서 정의한 변수 name의 값을 성공적으로 출력한다.
    • 이 예시를 통해 함수가 중첩된 상황에서 파서가 어떻게 변수를 처리하는지 알 수 있다.
    • 이는 어휘적 범위 지정(lexical scoping)의 한 예이다.

      여기서 "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다.

  • 중첩된 함수는 외부 범위( 스코프 )에서 선언한 변수에도 접근할 수 있다.

클로저 기초

  • 클로저를 어떻게 구분할까?

    • const globalVar = '전역 변수';
      
       function outerFn() {
         const outerFnVar = 'outer 함수 내의 변수';
         const innerFn = function() { 
           return 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.';
        }
           return innerFn;
      }
    • 함수 outerFn에서는 변수 globalVar에 접근할 수 있다.

    • 함수 innerFn에서는 변수 globalVar와 함수 outerFn 내부의 outerFnVar에 접근할 수 있다.


  • 클로저는 왜 중요할까?

    • 클로저의 함수는 어디에서 호출되느냐와 무관하게 선언된 함수 주변 환경에 따라 접근할 수 있는 변수가 정해지기 때문.

    • const globalVar = '전역 변수';
      
       function outerFn() {
         const outerFnVar = 'outer 함수 내의 변수';
         const innerFn = function() { 
           return 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.';
        }
           return innerFn;
      }
      
       const innerFnOnGlobal = outerFn();
       const message = innerFnOnGlobal();
       console.log(message); // innerFn에서는 변수 globalVar와 함수 outerFn 내부의 outerFnVar에 접근할 수 있습니다.
    • innerFnOnGlobalouterFn 내부의 innerFn의 주소값을 가진다.

    • 그다음 줄에서 innerFnOnGlobal을 호출.

    • 이때, innerFnOnGlobalinnerFn 밖에 있기 때문에 outerFnVar에는 접근하지 못한다고 생각할 수 있는데, 실제 접근할 수 있다.

      • innerFnOnGlobalouterFnVar에 접근 못하지 않나요?
        내부 스코프에 접근 할 수 없으니깐?

    • 왜 접근할 수 있을까?

      • innerFn 함수가 최초 선언되었던 환경에서는 outerFnVar에 접근할 수 있기 때문
      • innerFnOnGlobalinnerFn의 주소값을 가지고 있고, innerFn은 클로저로서 outerFnVar에 접근할 수 있기 때문.
    • 위에서 설명한 어휘적 환경(Lexical Environment)을 뜻한다.


클로저 활용

  • function makeAdder(x) {
        let y = 1;
        return function(z) {
          y = 100;
          return x + y + z;
        };
      }
    
      let add5 = makeAdder(5);
      let add10 = makeAdder(10);
      //클로저에 x와 y의 환경이 저장됨
    
      console.log(add5(2));  // 107 (x:5 + y:100 + z:2)
      console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
      //함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
  • 이 예제에서 단일 인자 x를 받아서 새 함수를 반환하는 함수 makeAdder(x)를 정의했다.

  • 반환되는 함수는 단일인자 z를 받아서 xyz의 합을 반환한다.


데이터를 보존하는 함수

  • 클로저를 활용하면 클로저의 함수 내에 데이터를 보존해 두고 사용할 수 있다

    • 일반적으로 함수 내부에 선언한 변수에는 접근할 수 없다.

    • 매개변수도 마찬가지다.

    • function getFoodRecipe (foodName) {
        let ingredient1, ingredient2;
        return `${ingredient1} + ${ingredient2} = ${foodName}!`;
      }
      
      console.log(ingredient1); // ReferenceError: ingredient1 is not defined (함수 내부에 선언한 변수에 접근 불가)
      console.log(foodName); // ReferenceError: foodName is not defined (매개변수에 접근 불가)
  • 클로저를 응용하면, 함수 내부에 선언한 변수에 접근할 수 있고, 매개변수에도 접근할 수 있다.

    • 기존 함수 내부에서 새로운 함수를 리턴하면 클로저로서 활용할 수 있다.
    • 즉, 리턴한 새로운 함수의 클로저에 데이터가 보존된다.
  • 레시피를 제작하는 createFoodRecipe 함수

    • function createFoodRecipe (foodName) {
        const getFoodRecipe = function (ingredient1, ingredient2) {
          return `${ingredient1} + ${ingredient2} = ${foodName}!`;
        }
        return getFoodRecipe;
      }
      
      const highballRecipe = createFoodRecipe('하이볼');
      highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
      highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
      highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
    • 코드에서는 getFoodRecipe가 클로저로서 foodName, ingredient1, ingredient2에 접근할 수 있다.

    • 이때, createFoodRecipe('하이볼')으로 전달된 문자열 '하이볼'recipe 함수 호출 시 계속 재사용할 수 있다.

    • createFoodRecipe 가 문자열 ‘하이볼’을 “보존”하고 있기 때문.


커링

  • 커링은 여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위

    • function sum(a, b) {
        return a + b;
      }
      
      function currySum(a) {
        return function(b) {
          return a + b;
          };
      }
      
      console.log(sum(10, 20) === currySum(10)(20)) // true
    • sum 함수는 두 전달인자(10, 20)를 덧셈하는 함수

    • currySum은 첫 번째 전달인자 10을 리턴하는 함수로 전달

    • sumcurrySum이 같은 값을 리턴하기 위해서는 currySum 함수에서 리턴한 함수에 두 번째 전달인자 20을 전달하여 호출

    • 이렇게 커링을 활용한 currySum과 같은 함수를 커링 함수

      • 커링은 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이

모듈 패턴

  • JavaScript에 class 키워드가 없던 시절 모듈 패턴을 구현하기 위해서 클로저를 사용.

  • 모듈은 하나의 기능을 온전히 수행하기 위한 모든 코드를 가지고 있는 코드 모음.

  • 하나의 단위로서 역할을 합니다. 모듈은 다른 모듈에 의존적이지 않고 독립적이어야 한다.

    • function makeCalculator() {
        let displayValue = 0;
      
        return {
          add: function(num) {
            displayValue = displayValue + num;
          },
          subtract: function(num) {
            displayValue = displayValue - num;
          },
          multiply: function(num) {
            displayValue = displayValue * num;
          },
          divide: function(num) {
            displayValue = displayValue / num;
          },
          reset: function() {
            displayValue = 0;
          },
          display: function() {
            return displayValue
          }
        }
      }
      
      const cal = makeCalculator();
      cal.display(); // 0
      cal.add(1);
      cal.display(); // 1
      console.log(displayValue) // ReferenceError: displayValue is not defined
  • 코드는 계산기의 최소한의 기능을 모듈 패턴으로 구현

    • displayValue는 makeCalculator의 코드 블록 외에 다른 곳에서는 접근이 불가능.
    • cal의 메서드는 모두 클로저의 함수로서 displayValue에 접근할 수 있다.
    • 이렇게 데이터를 다른 코드 실행으로부터 보호하는 개념을 정보 은닉(information hiding)
    • 캡슐화(encapsulation)의 큰 특징
      • 클로저는 특정 데이터를 다른 코드의 실행으로부터 보호해야 할 때 용이

참조.

mdn_클로저

profile
Drop the Bit!

0개의 댓글