[JavaScript] Closure

smiley·2022년 4월 27일
0

JavaScript

목록 보기
5/6
post-thumbnail

Closure

클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상이다.


📌 우선 외부함수에서 변수를 선언하고 내부함수에서 해당 변수를 참조하는 형태의 간단한 코드를 보자.

var outer = function() {
  var a = 1;
  var inner = function() {
    return ++a;
  };
  return inner();
};

var outer2 = outer();
console.log(outer2); // 2

위 코드의 콜스택 및 실행 컨텍스트는 다음과 같다.

👉 outer 함수의 실행 컨텍스트가 종료되면, LexicalEnvironment에 저장된 식별자들(a, inner)에 대한 참조를 지운다 (가비지 컬렉터에 의해 소멸).
👉 함수의 실행 컨텍스트가 종료되기 이전에 inner 함수의 실행 컨텍스트가 종료된다.
👉 따라서 이후에 별도로 inner 함수를 호출할 수 없다.


📌 다음으로, inner 함수의 실행 결과가 아닌 "inner 함수 자체"를 반환하는 코드를 보자.

var outer = function() {
  var a = 1;
  var inner = function() {
    return ++a;
  };
  return inner;
};

var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3

** outer2 변수는 outer 함수의 실행 결과인 inner 함수를 참조하게 되므로, outer2()를 호출하면서 inner 함수가 실행된다.

위 코드의 콜스택 및 실행 컨텍스트는 다음과 같다.

👉 inner 함수가 실행될 시점에 outer 함수는 이미 실행이 종료된 상태이다.
👉 하지만 전역 컨텍스트 속 outer2 변수가 inner 함수를 참조하고 있기 때문에 가비지 컬렉터는 a를 수집하지 않는다 (가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값을 수집 대상에 포함시키지 않는다).
👉 따라서 outer 함수의 실행이 종료된 이후에도 inner 함수를 호출할 수 있게 된다.

즉, 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달한 경우, A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말한다.


📚 클로저의 활용

// case (1)
for (var i = 1; i < 6; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

🤔 위 코드의 출력은 어떻게 나타날까?

1부터 5까지 1초 간격으로 출력될 것으로 예상했지만,
정답은 6 6 6 6 6 이다.

그 이유는, 자바스크립트 엔진이 for문을 먼저 돌고 나서야 테스크 큐에 쌓인 비동기 콜백함수들을 실행시키기 때문이다.

setTimeout(()=> {
  console.log(i);
}, 1 * 1000);

setTimeout(()=> {
  console.log(i);
}, 2 * 1000);

setTimeout(()=> {
  console.log(i);
}, 3 * 1000);

setTimeout(()=> {
  console.log(i);
}, 4 * 1000);

setTimeout(()=> {
  console.log(i);
}, 5 * 1000);

즉, 테스크 큐 안에는 다음과 같은 비동기 콜백 함수들이 쌓이는데, 이때 함수 속 i는 콜백함수가 실행될 때 결정된다. i는 이미 for문이 돌고 난 후의 6 값을 가지므로 6이 총 5번 찍히게 된다.

그렇다면 어떻게 의도한 대로 코드를 설계할 수 있을까?

🤩 클로저 관계를 활용하면 된다!

// case (2)
function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}

for (var i = 1; i < 6; i++) {
  setTimer(i);
}

앞서 언급하였듯, 클로저란 어떤 함수 A에서 선언한 변수 a를 외부로 전달한 경우 A의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않도록 도와주는 것을 의미한다. 따라서 각 i는 해당 함수가 호출될 당시의 i를 기억하게 된다.

function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}
setTimer(1);

function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}
setTimer(2);

function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}
setTimer(3);

function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}
setTimer(4);

function setTimer(j) {
  setTimeout(()=> {
    console.log(j);
  }, j * 1000);
}
setTimer(5);

즉, 위와 같은 코드가 실행되며 1 2 3 4 5 가 출력된다.


(출처 : 정재남(2019), [코어 자바스크립트], 위키북스)

profile
하루하루 성장하는 개발자입니다. 배운 것을 기록하고 있습니다.

0개의 댓글