18. Closure

함수와 그 함수가 선언한 렉시컬 환경의 조합, 클로저

Index

클로저와 렉시컬 환경

클로저의 활용

혼동하기 쉬운 케이스



A closure is the combination of a function and the lexical environment within which that function was declared.

​ Mozilla Developer Network. (n.d.). Closures. Retrieved December 27, 2024

1. 클로저와 렉시컬 환경

const x = 1;

// ①
function outer() {
  const x = 10;
  const inner = function () {
    console.log(x);
  }; // ②
  return inner;
}

// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer();
innerFunc(); // 10

🧐 Q. Outer 함수의 지역변수 x의 값인 10이다. 이미 생명 주기가 종료되어 실행 컨텍스트 스택에서 제거된 outer 함수의 지역 변수 x가 다시 부활이라도 한 듯 동작하고 있다. 도대체 이는 어떻게 된건인걸까?

Answer) outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer함수의 렉시컬 환경까지 소멸하지는 않는다.

outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고,

inner 함수는 전역 변수 innerfunc에 의해 참조되고 있으므로 가비지컬렉션의 대상이 되지 않는다.

Photo of Nested functions and lexical environments

Nested functions and lexical environments

Ungmo Lee. (2020). Modern Javascript DeepDive. wikibooks. p.396.



2. 클로저의 활용

🧐 Q. 근본적으로 클로저를 왜 사용할까?

Answer)

첫째은 상태 유지(state preservation)를 위해서

=> “변수의 값이 특정 함수가 호출되기 전까지 변경되지 않고 유지하기 위해서”

둘째은 데이터 보호(data encapsulation) 및 제어(controlled access)를 위해서

=> “오로지 특정 함수만 해당 변수의 값을 변경하기 위해서”

const increase = (function () {
  let num = 0;

  return function () {
    return ++num;
  };
})();

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3


🧐 Q. 즉시실행 함수 내 선언된 변수와 생성자 함수가 생성할 인스턴스의 프로퍼티간 차이는 무엇일까?

// (1) 즉시실행함수 내 선언된 변수 (private)

const Counter = (function () {
  let num = 0; // 클로저 변수

  function Counter() {}
  Counter.prototype.increase = function () {
    return ++num;
  };

  Counter.prototype.decrease = function () {
    return num > 0 ? --num : 0;
  };

  return Counter;
}());

const counter = new Counter();

console.log(counter.increase()); // 1
console.log(counter.num); // undefined
conuter.num = 10;
console.log(counter.num); // 10
⭐️ 헷갈렸던 부분
// 이는 counter객체에 num 프로퍼티 ≠ 클로저 변수 num과는 관계가 없다.
// 클로저 변수 num과 인스턴스 프로퍼티 num은 서로 다른 렉시컬 환경(Lexical Environment)에 존재하며, 완전히 별개의 개체로 동작한다.


// (2) 생성자 함수가 생성할 인스턴스의 프로퍼티 (public)

const Counter = (function() {
  function Counter() {
    this.num = 0; // 인스턴스 프로퍼티로 정의
  }

  Counter.prototype.increase = function() {
    return ++this.num; // this.num 참조
  };

  Counter.prototype.decrease = function() {
    return this.num > 0 ? --this.num : 0; // this.num 참조
  };

  return Counter;
}());

const counter = new Counter();
console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0
console.log(counter.num); // 0 (외부에서 접근 가능)


🧐 Q. 렉시컬 환경을 공유하는 클로저를 만들 수 없을까?

const counter = (function() {
let counter = 0;

return function (predicate) {
  counter = predicate(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
/*
즉시 실행 함수로 호출하면 렉시컬 환경을 공유하는 클로저가 생기고
여기에 증가, 감소 함수를 인자로 넣음으로써 해당 클로저를 공유할 수 있다.
/*



3. 혼동하기 쉬운 케이스

(1)
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs[i] = function() { return i; };
}
// i가 증가된 상태로 끝나고,
for (var j = 0; j < funcs.length; j++) {
  console.log(funcs[j]()); // 3, 3, 3
}


(2)
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs[i] = (function(i) {
    return function() {
      return i;
    };
  }(i));
}

for (var j = 0; j < funcs.length; j++) {
  console.log(funcs[j]()) // 0, 1, 2
}
// 즉시실행함수가 반환한 함수는 클로저이다.자신이 언된 환경(스코프)에 있는 변수에 접근할 수 있는 함수를 말한다.
// 즉, 함수가 외부 함수의 변수에 접근하거나 참조를 유지할 수 있으면 클로저라고 할 수 있다
// 즉시 실행 함수 내부의 i 값은 외부에서 접근할 수 없지만, 반환된 함수는 이 값을 계속 참조합니다.



(1-2) keyword 변화 (var  let)
let funcs = [];
for (let i = 0; i < 3; i++) {
  funcs[i] = function() { return i; };
}
// i가 증가된 상태로 끝나고,
for (let j = 0; j < funcs.length; j++) {
  console.log(funcs[j]()); // 0, 1, 2
}
// 1. for 루프의 매 반복마다 새로운 블록 스코프가 생성된다.
// 2. let i는 해당 블록 스코프에서 새로 선언되므로, 반복마다 다른 i 변수가 존재한다.
// 3. funcs[i]에 저장된 함수는 각각의 반복에서 생성된 고유의 i를 참조한다.
// 4. 따라서 각 함수는 자신이 생성된 반복에서의 i값을 클로저로서 기억하고 반환한다.

Photo of Blocklevel Scope Closure

Blocklevel Scope Closure

Ungmo Lee. (2020). Modern Javascript DeepDive. wikibooks. p.415.




reference: 모던자바스크립트 Deep Dive 24장. 클로저