카테고리 없음

자바스크립트 클로저

규몽 2020. 11. 23. 14:11

 


클로저

 

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

 

클로저가 무엇인지 클로저의 정체를 완전히 드러낼 코드를 보자.

 

function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2
view raw 1.js hosted with ❤ by GitHub

 

함수 bar( )는 foo( )의 렉시컬 스코프에 접근할 수 있고, bar( ) 함수 자체를 값으로 넘긴다. 이코드는 bar를 참조하는 함수 객체 자체를 반환한다. foo( )를 실행하여 반환한 값을 baz라 불리는 변수에 대입하고 실제로는 baz( ) 함수를 호출했다. 이 경우에 함수 bar는 함수가 선언된 렉시컬 스코프 밖에서 실행됐다.

 

일반적으로 foo( )가 실행된 후에는 foo( )의 내부 스코프가 사라졌다고 생각할 것이다. 그러나 선언된 위치 덕에 bar( )는 foo( ) 스코프에 대한 렉시컬 스코프 클로저를 가지고, foo( )는 bar( )가 나중에 참조할 수 있도록 스코프를 살려둔다.

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

 

어떤 방식으로 내부 함수를 자신이 속한 렉시컬 스코프 밖으로 수송하든, 함수는 처음 선언된 곳의 스코프에 대한 참조를 유지한다. 어디에서 해당 함수를 실행하든 클로저가 작용한다.

 

반복문과 클로저

클로저를 설명하는 가장 흔한 사례는 for 반복문이다.

 

 

for(var i=1; i<=5;i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
view raw 1.js hosted with ❤ by GitHub

이 코드의 목적은 1,2,3,4,5까지 한 번에 하나씩 일 초마다 출력하는 것이다. 근데 결괏값은 일 초마다 6만 5번 출력된다.

 

왜 그럴까?

 

먼저 setTimeout()에 인자로 넘긴 함수 timer( )는 모두 0.1초 뒤에 호출될 것이다. 그 0.1초 동안에 이미 반복문이 모두 순회되면서 i값은 이미 6이 된 상태. 그 때 함수 timer( )가 호출되면서 이미 6이 되어버린 i를 참조하는 것이다.

 

어떻게 해야 될까?

 

반복마다 각각의 i 복제본을 잡아두는 것이다. 그러나 반복문 안 총 5개의 함수들은 반복마다 따로 정의됐음에도 모두 같이 글로벌 스코프 클로저를 공유해 해당 스코프 안에는 오직 하나의 i만이 존재한다. 따라서 모든 함수는 당연하게도 같은 i에 대한 참조를 공유한다. 무엇이 더 필요 한가? 필요한 것은 반복마다 하나의 새로운 닫힌 스코프와 각 스코프에 i값을 저장할 자체 변수가 필요하다.

 

let으로 선언된 변수는 한 번만 선언되는 것이 아니라 반복할 때 마다 선언되는 특성을 이용해 아래와 같이 해결할 수 있다.

 

 

for(let i=1; i<=5; i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
view raw 1.js hosted with ❤ by GitHub