[JS] 클로저(Closure)

클로저(Closure)

클로저는 유효범위(Scope)를 기억하는 함수이다. 따라서 내부함수가 외부함수의 지역변수를 기억하여 접근할 수 있다.

외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수는 외부함수의 지역변수에 접근할 수 있다. 다시 말해서, 클로저란, 내부함수가 외부함수의 지역변수에 접근할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는 특성을 의미한다.


유효범위(scope)

클로저에 대해 더 자세히 알아보기 전에 유효범위에 대해 알아보자.

변수는 크게 지역변수와 전역변수가 있다. 지역변수는 함수 내에서만 생명을 가지고, 함수가 종료되면 함수 내의 변수는 사라진다. 이렇게 함수 단위의 지역으로 변수의 유효 범위가 정해지는 것을 유효 범위(scope)라고 한다.

유효범위 Example

1
2
3
4
5
6
7
8
9
10
11
var global = 20; // 전역변수

function func() {
var local = 10; // 지역변수
document.write(global + '<br>'); // 출력결과 > 20
document.write(local + '<br>'); // 출력결과 > 10
}

func();
document.write(global + '<br>'); // 출력결과 > 20
document.write(local + '<br>'); // 출력결과 > error

함수 밖의 전역 변수 global은 코드 전체에서 값이 유효하지만 함수 내의 지역 변수인 local은 함수 밖에서 사용할 수 없다. 함수가 종료되면 지역 변수의 유효범위가 종료되기 때문이다.

그러나 함수가 종료되어도 변수를 유지시킬 수 있는데, 그 방법이 바로 클로저이다.


클로저 Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function outFunc() {
var value = 0;

//내부 함수(클로저)
function inFunc() {
return value += 1;
}

return inFunc;
}

var test = outFunc();
document.write(test() + '<br>'); // 1
document.write(test() + '<br>'); // 2
document.write(test() + '<br>'); // 3
  • (줄 12) : 클로저 함수를 리턴받아 새로운 변수에 대입하면 outFunc()가 종료된 후에도 해당 함수의 지역변수 값을 계속 참조하고 변수 값이 생존하게 된다.


클로저 Example2

1
2
3
4
5
6
7
8
9
function outer() {
var title = 'hello world';
return function() {
alert(title);
}
}

inner = outer();
inner();

8행에서 outer함수가 호출되었고, 그 결과가 inner변수에 담겼다. 그리고 9행으로 넘어오면 outer함수는 종료되었기 때문에 이 outer함수의 지역변수인 title은 소멸되는 것이 자연스럽다. 하지만 9행에서 inner함수를 호출했을 때, hello world가 잘 출력된다. 이것은 외부함수의 지역변수인 title이 소멸되지 않았다는 것이다.

이처럼 내부함수가 외부함수의 지역변수에 접근할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는다.


for문과 클로저

for문으로 클로저를 생성할 경우 실수가 빈번하게 발생한다. 다음 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
var arr = [];

for(var i=0; i<3; i++) {
arr[i] = function() {
return i;
}
}

document.write(arr[0]() + '<br>'); // 3
document.write(arr[1]() + '<br>'); // 3
document.write(arr[2]() + '<br>'); // 3

위 코드는 3만 세 번 출력한다. 클로저를 사용하려면 아래의 코드처럼 수정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [];

for(var i=0; i<3; i++) {
(function(index){
arr[index] = function() {
return index;
}
})(i); // 익명 함수
}

document.write(arr[0]() + '<br>'); // 0
document.write(arr[1]() + '<br>'); // 1
document.write(arr[2]() + '<br>'); // 2

위 코드는 정상적으로 0, 1, 2를 출력한다. for문 안의 처리문을 즉시 실행 익명 함수로 분리시키고 클로저가 for문의 i가 아닌 익명함수의 index변수를 참조하도록 한다.

참고로 ECMAScript 2015 (ES6)의 새로운 변수 선언문인 let 키워드로 변수를 선언하면 변수의 스코프가 블록 단위(기존 JS는 함수 단위이다.)로 엄격해지므로 이러한 문제가 발생하지 않는다. 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
let arr = [];

for(let i=0; i<3; i++) {
arr[i] = function() {
return i;
}
}

document.write(arr[0]() + '<br>'); // 0
document.write(arr[1]() + '<br>'); // 1
document.write(arr[2]() + '<br>'); // 2
Share