[자바스크립트]클로져(closure)
in Devlog on JavaScript
클로져는 사실 우리들의 코드에 항상 있지만, 잘 눈치채지 못하고 있는 요소 중 하나이다. 평소엔 별 문제 없더라도 모르면 해결하기 어려운 상황에 처하는 경우가 한번씩 생기곤한다.
TL;DR 함수가 속한 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 그 스코프에 접근할 수 있게 하는 것.
클로져
클로져의 예를 들어보자.
var a = 32;
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2
클로져의 예이다. 당연히 같은 이름의 변수를 생성하지 않는 것이 좋지만, 예시니까 가벼운 마음으로 보자.
bar라는 함수가 그 자체로 반환되었다고 생각한다면 외부의 변수 a에 접근하는 것인지 충분히 헷갈릴 수 있는 요소이다. 실제로는 foo의 스코프가 죽지않고
남아있다. (자바스크립트도 gc(가비지컬렉터)
가 있다. 일을 안하는 것이아니라 bar가 참조할 수 있도록 남겨놓은 것이다.)
바로 이 남겨진 스코프
가 다른 곳에서 참조할 수 있게 된 것이 클로져이다.
아주 흔한 실수
클로져를 설명할 때 빠지지 않는 예제가 하나 있다. 바로 반복문
이다.
for(var i=0;i<5;i++){
setTimeout( function timer(){
console.log(i);
}, i*1000);
}
이 함수의 목적은 아마 0부터 4까지 한번씩 출력하는 것이었을 것이다. 하지만, 이 함수의 결과는 1초마다 5
를 5번 출력하고 마치게된다.
이유는 뭘까? 반복문이 끝나고 나서야 타임아웃 함수가 작동한다.(참고로 i*1000이 0으로 바뀐다고해도 마찬가지다.) 그러므로 반복문의 마지막 i값인 5가 5번 출력되는 것이다.
5개의 setTimeout함수가 모두 같은 i를 바라보고있기 때문인데, 이런 경우 해결방법은 뭘까? 어떻게 해결해야 원하는 결과를 낼 수 있을까?
즉시호출함수표현식(IIFE)
for(var i=0; i<5; i++){
(function(j){
setTimeout( function timer(){
console.log(j)
}, j*1000)
})(i);
}
문제는 해결됬다. 매 setTimeout 함수 호출시마다 j가 포함된 새 닫힌 스코프를 만드니 이제 원하는대로 출력된다. 그럼 같은 원리를 let으로 구현할 수 있지 않을까? (let은 닫힌 스코프를 만든다.)
블록 스코프
for(var i=0; i<5; i++){
let j = i;
setTimeout( function timer(){
console.log(j);
},j*1000);
}
이것역시 의도대로 동작한다. 근데 잠깐. 하나 더 보자.
for(let i=0; i<5; i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
let이 반복문에서 사용될 때는 한 번만 선언되는 것이 아니라 반복할 때 마다 선언된다. 매번 새로운 값으로 닫힌 스코프를 만들어 낼 수 있다.
내가 봤을 땐 클로져를 이해하고 있고, let도 이해하고 있으며, 훨씬 간결하고 이해가능한 마지막 코드가 가장 아름다워보인다.
마치며
공부를 많이하여 아는 것이 많아지면 이해하기도 쉽고 기능적으로도 온전한 코드를 짤 수 있다. 이게 프로그래머가 공부하는 이유가 아닐까.
2023년 새해에는 성장하고 함께하고 싶다면?
Pre A 단계 이상의 스타트업 C 레벨들이 모여서 커뮤니티를 만들었습니다. 같이 스터디하고 친해질 일잘러를 찾습니다.