자바스크립트 클로저(Closure)

Hailey Choi
7 min readDec 6, 2020

--

클로저(Closure)는 내부함수가 외부함수의 맥락(Context)에 접근 할 수 있는 것이라고 할 수 있습니다. 예제를 통해 여기서 말하는 내부함수와 외부함수가 무엇인지 알아보겠습니다.

function outer () {
var title = '클로저';
function inner () {
alert(title); // 클로저
}
inner();
}
outer();

outer라는 함수 안에 inner 함수가 정의되어 있습니다. 여기서 outer 함수가 외부함수이고 inner 함수가 내부 함수입니다. 내부함수는 외부함수 안에서만 유효하기 때문에 외부함수 밖에서 내부함수에 접근할 수 없다는 것이 기본 맥락입니다.

나아가 위 예제에서 우리가 더 살펴보아할 부분은 변수 title이 선언된 위치입니다. 현재 내부함수는 inner에서 title 변수를 사용하고 있는데, 내부 함수에서 선언된 title 변수가 존재하지 않기 때문에 자신을 둘러싼 외부함수에서 title 변수를 찾게 됩니다. outer 함수에서 선언된 title 변수가 내부함수 inner 의 title 값으로 쓰이는 것이죠.

즉, 내부함수에서 외부함수의 지역변수에 접근할 수 있다는 의미입니다. 이것이 클로저입니다.

클로저(Closure) 특징

클로저는 독특한 특징을 가지고 있는데요. 외부 함수가 return으로 종료된 이후에도 내부함수에서 외부 함수에 접근할 수 있다는 것입니다. 예제로 확인해보겠습니다.

function outer () {
var title = '클로저';
return function () {
alert(title); // 클로저
}
}
var inner = outer();
inner();

위의 예제를 분석해봅시다.

outer 함수는 alert를 띄우는 내부함수를 return하고 있습니다. 변수 inner는 outer 함수의 return 값을 가지고 있으므로, inner 변수에 값이 담기는 순간, outer 함수는 생을 마감했다고 볼 수 있습니다. return이 일어났으니까요.

inner 변수는 이러한 함수를 가지게 되겠죠.

function () {
alert(title);
}

아이러니한 점은 inner 함수가 실행될때 title이라는 변수를 참조해야하는데 title 변수가 선언된 outer 함수는 이미 생을 마감해버렸습니다. 그렇다면 title은 undefined가 될까요? 아닙니다. alert는 외부함수에서 선언한 title 값인 “클로저”를 띄우게 됩니다. 이게 바로 클로저의 특징이죠.

외부함수에서 파생된 내부함수는 이미 생을 마감한 외부함수에 접근이 가능합니다.

이러한 특징을 가진 클로저가 어떠한 상황에서 쓰이는지 알아보겠습니다.

예제 1 — 정보 은닉화 효과내기(private method)

function handleTitle(title) {
return {
getTitle: function () {
return title;
},
setTitle: function (prevTitle) {
title = prevTitle;
},
}
}
var test1 = handleTitle('클로저 테스트');
var test2 = handleTitle('자바스크립트를 잘 하는 법');

handleTitle라는 외부함수안에 getTitle과 setTitle이라는 내부함수를 가지는 객체가 return되고 있습니다. 내부함수에서 쓰이는 title이라는 값은 외부함수인 handleTitle의 매개변수인 title를 가리키고 있습니다.

그리고 각각 “클로저 테스트”와 “자바스크립트 잘 하는 법”이라는 title값을 가지는 handleTitle을 실행하여 각각 test1과 test2라는 변수에 담았는데요. test1과 test2는 getTitle과 setTitle 함수를 가지는 객체겠죠? 각각의 객체에서 getTitle이라는 함수를 호출해보겠습니다.

alert(test1.getTitle()); // '클로저 테스트'
alert(test2.getTitle()); // '자바스크립트를 잘 하는 법'

여기서 getTitle은 각각 다른 버전의 클로저를 참조하기 때문에 그에 따른 title이 출력됩니다. 이 말은 setTitle함수를 이용하여 test1의 title값을 “클로저 테스트 성공”이라고 변경하여도 test2에는 아무런 영향이 가지않는다는 것입니다. test2의 title 값은 그대로 “자바스크립트 잘 하는 법”이 됩니다.

즉, 각 클로저는 그들 고유 클로저의 변수를 참조하기 때문에 하나의 클로저에서 변수 값을 변경해도 다른 클로저의 값에는 영향을 주지 않습니다.

이러한 코드 패턴의 장점은 외부에서 title 값이 무작위로 바뀌는 것을 방지할 수 있다는 것입니다. title 값을 변경하려면 각 클로저 버전에서 setTitle이라는 메소드를 통해서만 변경 가능한 것이죠. 또한 setTitle 메소드에서 title 값이 String Type인지 확인하는 코드를 추가할 수도 있습니다.

실제로 클로저가 private method 기능을 제공하는 것이 아니라 클로저를 통해서 이러한 효과를 낼 수 있다는 것입니다.

예제 2 — 변경된 최신 상태를 적용하기

for (var i=0; i<10; i++){
setTimeout(function(){
console.log(i);
}, 100);
}

100ms 뒤에 0부터 9까지 콘솔에 찍히는 코드를 작성해 보았습니다. 콘솔에 0부터 9까지 찍힐 것이라고 예상했지만 결과는 10이라는 숫자만 10번 찍혔습니다. 왜 일까요? 그 이유는 100ms 시간동안 이미 반복문이 종료되어 i가 10이 되었고 이후에 setTimeout 비동기 함수의 console.log(i)가 실행되었기 때문이죠.

반복문이 실행되는 순간 setTimeout같은 비동기 함수는 콜스택(Call Stack)이 아닌 콜백큐(Callback Queue)에서 대기합니다. 콜스택(Call Stack)에서 i가 10이되고 나서야 이벤트 루프(Event Loop)는 콜백큐(Callback Queue)의 console.log(i)를 콜스택(Call Stack)으로 밀어넣습니다. 이 시점에 반복문은 이미 종료되었기 때문에 i는 모두 10이 적용되는 것이죠.

위의 과정을 더 자세히 알고 싶으시면 자바스크립트는 어떻게 동작하나요? 를 참고해주세요.

이번에는 위의 예제를 클로저로 해결해보겠습니다.

for (var i=0; i<10; i++){
function outer(j) {
setTimeout(function () {
console.log(j);
}, 100);
}
outer(i);
}

내부함수가 생을 마감한 외부함수에 접근할 수 있다는 클로저의 특징을 이용하여 콘솔에 0부터 9까지 찍히는 결과를 확인할 수 있습니다.

반복문이 실행될때 마다 outer 함수가 실행되고 있고 인자로 i 값을 전달하고 있습니다. 이 때 i 값은 각각 0 부터 9까지가 되겠죠? outer 함수 내부에 setTimeout 함수가 있습니다. setTimeout 함수는 외부 함수인 outer의 지역변수에 접근할 수 있습니다. 여기서 지역변수는 outer 함수의 매개변수인 j입니다. j는 outer 함수의 i 인자를 가지므로 각각의 j는 0부터 9가 된다는 것을 알 수 있습니다.

참고

--

--