console.log

[모던 자바스크립트 Deep Dive] 24 장 본문

개발공부/JavaScript

[모던 자바스크립트 Deep Dive] 24 장

foresight 2023. 8. 30. 19:23

24. 클로저

클로저

  • 함수형 프로그래밍 언어(하스켈, 리스브, 얼랭, 스칼라) 에서 사용되는 중요한 특성
  • ECMAScript에 정의가 나와있지 않음
  • MDN에서는 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다. 라고 정의
  • const x = 1;
    
    function outerFunc() {
      const x = 10;
      function innerFunc() {
        console.log(x); // 10
      }
      innerFunc();
    }
    outerFunc();
    
    • innerFunc 함수는 outerFunc 내부 중첩 함수이기 때문에 outerFunc의 x변수에 접근 가능 (렉시컬 스코프)

렉시컬 스코프

  • 함수를 어디서 호출했는지 x 어디에 정의했는지 o
  • const x = 1;
    
    function foo() {
      const x = 10;
      bar();
    }
    
    function bar() {
      console.log(x);
    }
    
    foo(); // 1
    bar(); // 1
    

함수 객체의 내부 슬롯 [[Environment]]

  • 렉시컬 스코프가 가능하기 위해서는 상위 스코프(자신이 정의된 환경)을 기억해야 함
  • 함수는 자신의 내부 슬록 [[Environment]] 에 상위 스코프의 참조를 저장
  • 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킴
    • 함수 정의가 평가되어 함수 객체를 생성하는 시점은 상위 함수가 평가 또는 실행되고 있는 시점
  • 함수 내부에서 정의된 함수 표현식은 외부 함수 코드가 실행되는 시점에 생성
  • image
    1. 함수 실행 컨텍스트 생성

    2. 함수 렉시컬 환경 생성

      2-1. 함수 환경 레코드 생성

      2-2. this 바인딩

      2-3. 외부 렉시컬 환경에 대한 참조 결정 ([[Environment]] 에 저장된 렉시컬 환경의 참조가 할당됨)

클로저와 렉시컬 환경

  • const x = 1;
    // 1.
    function outer() {
      const x = 10;
      const inner = function () {
        console.log(x);
      }; // 2.
      return inner;
    }
    // outer 함수를 호출하면 중첩 함수 inner를 반환한다.
    // 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
    const innerFunc = outer(); // undefined  3.
    innerFunc(); // 10  4.
    
    • 3에서 outer 함수를 호출하면 inner함수를 반환하고 생명주기 마감 → outer 함수의 실행 컨텍스트는 스택에서 제거

    • 이때, 지역 변수 x도 생명 주기 마감되어 접근할 수 없어야 하지만 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수 참조 가능 → 이러한 중첩 함수를 클로저 라고 한다.

    • outer 함수의 실행 컨텍스트가 제거되지만 outer 함수의 렉시컬 환경은 남아있음 (누군가 참조 중인 공간은 가비지 컬렉션 대상 안됨 !)

    • 상위 스코프의 어떤 식별자도 참조하지 않으면 클로저가 아님 !

    • function foo() {
        const x = 1;
      
        function bar() {
          debugger;
          console.log(x);
        }
        bar();
      }
      
      foo();
      
      • 외부 함수 foo 보다 bar의 생명 주기가 짧기 때문에 클로저라고 하지 않음
  • 즉, 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고, 외부 함수보다 생명 주기가 길 때 한정

클로저의 활용

  • 상태를 안전하게 변경하고 유지
  • 상태를 안전하게 은닉, 특정 함수만 상태 변경 허용
  • // 카운트 상태 변수
    let num = 0;
    
    // 카운트 상태 변경 함수
    const increase = function () {
      // 카운트 상태를 1만큼 증가시킨다.
      return ++num;
    };
    
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3
    
    • 오류 발생 가능성 있음
      • 카운트 상태는 increase 함수 호출 전까지 변경되지 않고 유지되어야 함
      • 카운트 상태는 increase 함수만 변경 가능해야 함
    // 카운트 상태 변경 함수
    const increase = function () {
      // 카운트 상태 변수
      let num = 0;
    
      // 카운트 상태를 1만큼 증가시킨다.
      return ++num;
    };
    
    // 이전 상태를 유지하지 못한다.
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3
    
    • 카운트 상태를 increase 함수의 지역 변수로 변경
    • increase 함수만이 변경 가능
    • 하지만 이전 상태 유지 못함
    // 카운트 상태 변경 함수
    const increase = (function () {
      // 카운트 상태 변수
      let num = 0;
      // 클로저
      return function () {
        // 카운트 상태를 1만큼 증가시킨다.
        return ++num;
      };
    })();
    
    // 이전 상태를 유지하지 못한다.
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3
    
    • 즉시 실행 함수가 호출되고 increase 변수에는 즉시 실행 함수가 반환한 함수가 할당됨
    • 따라서 num 변수는 재차 초기화 될 일이 없고 외부에서 접근 불가