GitHub
©-2 kd02109 All rights reserved.

Closure(클로저)

  • javascript
  • closure
February 28, 2023

클로저?

  • 클로저는 자바스크립트의 고유 개념이 아니다. 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(예: 하스켈, 리스프, 얼랭, 스칼라 등)에서 사용되는 중요한 특성이다.
  • 클로저는 ESCMAScript 사양에 등장하지 않는다. 하지만 MDN에서 다음과 같이 정의하고 있다.
  • 클로저는 ‘둘러싸여진 상태의 참조’와 함께 다발로 묶여진 함수의 콤비네이션이다. 바꿔 말하면, 클로저는 내부 함수로부터 외부함수에의 접근권한을 준다. 클로저는 함수 생성 시점에 언제나 생긴다.

렉시컬 스코프

  • 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프)라 한다.

    const x = 1;
     
    function foo() {
      const x = 10;
      bar();
    } // foo 와 전역컨텍스의 사이의 클로저가 생성
     
    function bar() {
      console.log(x);
    } // bar 와 전역컨텍스의 사이의 클로저가 생성
     
    foo(); // ?  => 1
    bar(); // ?	 => 1
  • foo, bar 함수는 모두 전역에서 정의된 함수이다. 함수의 상위 스코프는 어디서 정의했느냐에 따라 결정되므로 foo 함수와 bar 함수의 상위 스코프는 전역이다. 렉시컬 환경의 “외부 렉시컬 환경에 대한 참조” 에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.

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

  • 함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.
  • 이는 “외부 렉시컬 환경에 대한 참조”에 저장될 참조값이다.

클로저와 렉시컬 환경

  • 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.

    1. 함수를 반환하는 함수여야 한다.
    2. 반환된 함수는 외부함수의 변수를 참조해야 한다. (클로저는 내부함수에서 참조하고 있는 변수만을 남겨둔다.)
    const x = 1;
     
    function outer() {
      const x = 10;
      const inner = function () {
        console.log(x);
      };
      return inner;
    }
     
    // outer 함수를 호출하면 중첩 함수 inner를 반환한다.
    // 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
    const innerFunc = outer();
    innerFunc(); // 10
  • 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.

클로저의 활용

  • 클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다. 다시 말해 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해서이다.

    // 카운트 상태 변경 함수
    const increase = (function () {
      // 카운트 상태 변수
      let num = 0;
     
      // 클로저
      return function () {
        // 카운트 상태를 1만큼 증가 시킨다.
        return ++num;
      };
    })();
     
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3
  • 클로저 활용 예시

    // 함수를 반환하는 고차 함수
    // 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
    const counter = (function () {
      // 카운트 상태를 유지하기 위한 자유 변수
      let counter = 0;
     
      // 함수를 인수로 전달받는 클로저를 반환
      return function (aux) {
        // 인수로 전달 받은 보조 함수에 상태 변경을 위임한다.
        counter = aux(counter);
        return counter;
      };
    })();
     
    // 보조 함수
    function increase(n) {
      return ++n;
    }
     
    // 보조 함수
    function decrease(n) {
      return --n;
    }
     
    // 보조 함수를 전달하여 호출
    console.log(counter(increase)); // 1
    console.log(counter(increase)); // 2
     
    // 자유 변수를 공유한다.
    console.log(counter(decrease)); // 1
    console.log(counter(decrease)); // 0

커링

  • 여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위
  • 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이
function createFoodRecipe (foodName) {
  const getFoodRecipe = function (ingredient1, ingredient2) {
    return `${ingredient1} + ${ingredient2} = ${foodName}!`;
  }
  return getFoodRecipe;
}
 
const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
Content Table
  • 클로저?
  •    - 렉시컬 스코프
  •    - 함수 객체의 내부 슬롯 [[Environment]]
  •    - 클로저와 렉시컬 환경
  • 클로저의 활용
  •    - 커링
유사 배열 객체
실행 컨텍스트