javascript 클로저(closure)사용시 주의사항(3편)

클로저(closure)의 사용시 주의사항

클로저는 그 기묘한 특성?상 다양한 활용이 가능하지만, 무분별하게 사용시 성능문제나 코드의 난독화가 발생한다고 한다. 이번글에서는 클로저 사용시 결과 예측을 실수 할수있는 ,즉 헷갈리는 클로저 예제 소스를 통해서 주의할 점을 알아보도록 하겠다.

1.클로저를 선언하는 외부함수의 유효범위 스코프는 클로저에 의해 변경이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
function outerFunc(argNum){
var num = argNum;
return function(x){
num += x;
console.log('num:' + num);
};

}

var exam = outerFunc(40);
exam(5);
exam(-10);

결과

  • num:45
  • num:35

사실 주의할 점 까지는 아니지만, 클로저의 가장 중요한 개념이다. 클로저를 처음 접할때 가장 햇갈리는 부분이 이부분일 것이다.

클로저는 살아있다(alive)! 이 말이 무엇이냐면, outerFunc(40); 로 외부함수는 호출되고 종료되었지만, exam 이라는 변수에 클로저가 생성되고 할당 되었다. 이 클로저 exam 이 존재 하기 때문에 자바스크립트 엔진은 exam 이 생성될 때, 접근할 수 있는 영역에 대한 부분을 메모리에서 지우지 않고 보존하고 있다. 즉 exam 은 자신을 생성한 outerFunc의 살아있는 유효범위 스코프에 접근하여 값 접근 및 할당이 가능하다.

  1. 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우도 있다.

1
2
3
4
5
6
7
8
9
10
11
function func(){
var x = 1;
return {
func1 : function(){ console.log(++x); },
func2 : function(){ console.log(-x); }
};
}

var exam = func();
exam.func1();
exam.func2();

결과

  • 2
  • -2

이 예제도 그렇게 어려운 예제는 아닐 것이다. func()함수가 호출되며 하나의 객체를 리턴하고 그 객체는 func1, func2 두개의 메소드를 가지고 있다. 여기서 헷갈릴 가능성이 있는 부분은 리턴한 객체 exam 의 func1과 func2이 각각 다른 x에 접근 할 것이라 생각 할 수도 있다. 결과를 확인하면 exam.func1, 과 exam.func2과 동일한 x를 접근 하는 것을 확인 할 수 있다.

func1과 func2는 [동일한 함수 호출]에 의해 만들어 졌기 때문에 같은 x[동일한 함수 호출의 유효범위 스코프]에 접근 하는 것이다.

2번 예제를 통해 한가지 더 모호한 점은 클로저라는 어떤 대상의 구분이다.

한개의 클로저의 범위를 정의 한다면, 한개의 외부함수의 유효범위 스코프와 그 유효범위 스코프를 공유하는 내부함수(1개 이상)라고 볼 수 있을것 같다.

3.클로저를 정의하는 함수 내에서 루프를 이용하는 경우

1
2
3
4
5
6
7
8
9
10
11
12
function constfuncs(){
var funcs = [];
for(var i =0; i < 10; i++){
funcs[i] = function() { return i;}
}
return funcs;
}

var funcs = constfuncs();
funcs[0]();
funcs[4]();
funcs[9]();

결과

  • 10
  • 10
  • 10

결과를 0, 4, 9 가 리턴 될 것이라 예측 할 수도 있다. (필자 역시...) 여기서 중요한 점은 클로저와 연관된 유효범위 체인이 '살아있다'라는 것이다. 중첩함수의 유효범위에 대한 내부 사본이나 변수 바인딩의 스냅샷은 만들어지지 않는다. 이미 funcs0 이 호출되는 시점에는 자신을 생성할 때 호출된 constFuncs 함수의 var i 는 for문이 완벽하게 동작하고 종료되었기 때문에 10인 것이다.

  1. this와 arguments 사용시 주의점

이 부분은 클로저 사용시 주의사항이라기 보다는 내부함수(중첩함수) 사용시 공통적으로 주의해야 할 부분이다.

모든 함수 호출에는 this값이 있고, 외부 함수가 this 값을 별도로 변수로 저장하지 않으면 클로저는 외부 함수의 this값에 접근 할 수 없다. arguments는 키워드는 아니지만, 모든 함수 호출에 자동으로 선언된다. 클로저 함수는 자신만의 arguments를 가지고 있기 때문에, 외부 함수가 argumengs을 다른 이름의 변수에 저장하지 않는 한, 클로저는 외부 함수의 arguments에 접근 할 수없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function outerFunc1(){

console.dir(this);
console.dir(arguments);

return function(){
"use strict"; // this가 Window 전역객체로 할당 되는것을 강제로 막았다.
console.dir(this);
console.dir(arguments );
};
}

var test1 = outerFunc1(111, '가나다');

1
test1();

리턴된 클로저를 호출시 this 는 undefined(use strict를 통해 강제로 막았기 때문에) 그리고 arguments객체는 존재는 하지만 클로저 자신이 호출될때 생성된 arguments이기 때문에 외부함수 outerFunc1의 arguments에는 접근 할 수 없다.

해결방법 외부함수의 this와 arguments 객체를 외부함수 지역변수에 할당하자!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function outerFunc2(){

console.dir(this);
console.dir(arguments);

var outThis = this; // this를 클로저가 접근할 수 있도록 지역변수에 할당한다.
var outArgu = arguments; // arguments를 클로저가 접근할 수 있도록 지역변수에 할당한다.

return function(){
"use strict";
console.dir(outThis);
console.dir(outArgu );
};
}

var test2 = outerFunc2(222, 'abc');

1
test2();

외부함수의 this 와 arguments 를 지역변수에 할당하여 클로저가 접근할 수 있도록 할 수 있다.