클로저 (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 |