본문 바로가기

Javascript

클로저

클로저 (Closure)

You don't know JS 의 책을 인용하자면,
클로저는 렉시컬 스코프에 의존해 코드를 작성한 결과로 그냥 발생하는 것이다.

클로저는 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때도
이 스코프에 접근할 수 있게 하는 기능을 뜻한다.

클로저의 예

function foo() {
    var a = 2;
    function bar() {
        console.log(a); // 2 로 출력
    }
    return bar;
}
var baz = foo();
baz();

함수 bar() 는 foo() 의 렉시컬 스코프에 접근 할 수 있다.
foo() 는 bar() 함수 자체를 return 하면서 baz 에 넘긴다. 즉, bar 를 참조하는 함수 객체 자체를 반환 하는 것이다.
baz() 를 호출하면서 bar() 가 실행되고, bar() 는 함수가 선언된 렉시컬 스코프 밖에서 실행됐다.

일반적으로 foo() 가 실행 된 후에는 foo() 의 내부 스코프가 사라졌다고 생각할 수 있다.
foo() 를 이제 사용하지 않는 상황이라면 가비지 콜렉털로 인해 사라졌다고 보는게 자연스럽다.

그러나 '클로저' 로 인해 foo 의 내부 스코프는 여전히 bar 가 '사용 중'이므로 해제되지 않는다.
선언된 위치 때문에 bar 는 foo 스코프에 대한 렉시컬 스코프를 가지고, foo 는 bar 가 나중에 참조할 수 있도록
스코프를 살려둔다.

즉, bar는 여전히 해당 스코프에 대한 참조를 가지는데, 그 참조를 클로저라고 한다.

반복문과 클로저

for 문 내 비동기 함수의 문제

for(var i=1; i<10; i++) {
 setTimeout(function() {
    console.log(i); // 1초 마다 모두 10 이 찍힘
  }, i*1000);
}

위 실행 결과로는 모두 10이 찍힌다.
setTimeout 이 실행될 때에는 이미 i가 10 이 되어 있는 상태기 때문이다.

반복문 안의 총 10개의 함수들은 모두 같은 글로벌 스코프 클로저를 공유한다.
해당 스코프안에는 하나의 i 만이 존재하므로, 모든 함수는 같은 i 에 대한 참조를 공유하게 된다.

 

setTimeout 내의 function 은 실행될때 자신의 scope 에 i 가 없기 때문에 밖에서 i 를 찾게 된다.

이 때 i 는 global scope 에 속해있고 이미 10이 되어 있는 상태기 때문에 모두 10을 출력한다.

 

저 for 문의 결과는 아래와 같은 식이다.

비동기 함수는 실행되기 전까지는 내부 값이 결정되지 않는다. i 의 값은 실행될때 결정된다.

setTimeout(function() {
    console.log(i);
  }, 1*1000);
  
 setTimeout(function() {
    console.log(i);
  }, 2*1000);
  
 setTimeout(function() {
    console.log(i);
  }, 3*1000);
  
  ...

for문 내 비동기 함수 문제 해결

위의 문제는 클로저 특성을 사용해 해결 할 수 있다.

1 부터 10까지 차례대로 출력하기 위해선 어떻게 해야 할까
더 많이 닫힌 스코프가 필요하다!

 

아래 처럼 for 문 안에 함수를 실행하게 하고, 해당 함수의 인자로 i 를 넣어준다.

setTimeout 을 함수 스코프 안에 가둬(?)놨다.

setTimeout 내 function 을 실행할때는 이미 foo 함수 실행은 끝났지만, foo 함수 내의 j 는 참조가 가능하다.

클로저의 특성을 이용한 것이라고 할 수 있다.

for(var i=1; i<10; i++) {
 (function(j) {
   setTimeout(function() {
      console.log(j); // 이제 1부터 10 까지 잘 찍힌다.
    }, j*1000);
 })(i); // 이런 형태의 함수를 즉시실행 함수라고 한다.

}

// 아래의 코드를 간략한게 쓴거라고 생각하면 될 듯 하다.

for(var i=1; i<10; i++) {
 function foo(j) {
   setTimeout(function() {
      console.log(j); // 이제 1부터 10 까지 잘 찍힌다.
    }, j*1000);
 }
 
 foo(i);
}

반복 별 블록 스코프

위의 예에서는 setTimeout 함수의 문제를 반복마다 하나의 새로운 스코프를 생성해 해결했다.
즉, 실제 필요했던 것은 반복 별 블록 스코프다!
블록 스코프의 범위를 갖는 let을 이용하면 쉽게 해결 가능하다.

for(let i=1; i<=10; i++) {
    setTimeout(function() {
      console.log(i); // 1부터 10 까지 잘 찍힌다.
    }, i*1000);
}

let 선언문이 for 반복문 안에서 사용되면, 한 번만 선언되는 것이 아니라 반복할때 마다 선언된다.
해당 변수는 반복마다 초기화가 된다.

'Javascript' 카테고리의 다른 글

Typescript generic & util  (0) 2021.04.08
return function  (0) 2021.03.24
[You don't know JS] Chapter2 - 값  (0) 2021.02.25
[You don't know JS] Chapter1 - 타입  (0) 2021.02.23
스코프  (0) 2020.11.19