또는





자바스크립트에는 사람을 끌리게 하는 수수한 매력이 있다. 


그 매력들 중 하나가 클로저인 것 같다.


이 글은 클로저에 대해 간단히 정리한 글이다.


알아두면 프로그래밍 할 때, 정말 유용하다. !!!



 


JS 함수 사용 상에는 아래 세 가지 특징이 있다.


① 함수를 변수에 대입가능 --> 때문에 함수의 리턴값이 임의의 함수일 수도 있다.


② 함수의 중첩 사용을 허용한다.


③ 모든 변수에 어휘적 유효범위 규칙을 적용한다.


 


특별히 위 세 가지 사항이 모두 적용되는 경우로서,


어떤 함수 f1이 그 안에 중첩되어 정의된 함수 f2를 리턴한다고 가정하자.


이럴 경우, 재밌는 현상이 발생한다. 


 


f2가 어휘적으로 정의될 때 f2가 갖는 유효범위는 함수 f1의 호출객체를 포함한다.


f1함수는 종료되면서 호출객체f1을 반납하고 소멸되어야 한다. 그러나 한편 f2를


생각해보면 f2는 결과값으로서 리턴되는 것이므로 f1호출자에 의해 사용되어야 한다.


f2가 사용되려면 f2가 참조하는 모든 변수들 즉, f1호출객체가 계속 유지되어야 한다.


이런 이유로 f2와 f1호출객체 사이의 참조가 사라지지 않으므로 가비지컬렉터가


f1 호출객체를 소멸시키지 못하는 것이다. !!!


억지스럽지만, 영화 메트릭스에서 오라클과 네오 간의 참조의 끈 때문에 네오는


시스템속에서 가비지컬렉터인 스미스에 의해 소멸될 수 없는 존재? 메트릭스를 좋아하다보니..ㅋㅋ



 


정리하면, 중첩된 함수 f2가 리턴 값으로 사용되면 f2내에서 참조하고 있는


모든 객체(코드)들이 소멸되지 않고 살아남는다. 더군다나 f1호출객체 속의


모든 변수들은 코드 상으로 직접 접근하는 것이 절대 불가능하며 오로지 f2를


통해서만 간접적으로만 접근할 수 있다. 이것은 매우 강력한 캡슐화 도구이다.


 


살아남은 f1 호출객체에는 f1에서 정의된 변수 뿐만아니라 전달받은 전달인자들도


모두 들어 있다.


한편, 더 나아가 만약 f1이 복수 개의 중첩함수를 반환할 경우, 그 복수개의


중첩함수들은 같은 f1호출객체를 공유하게 되고 한 중첩함수에 의해 초래된 변화는


다른 중첩함수들에게도 영향을 미친다.



 


C와 같은 저수준 언어등에서는 변수를 저장할 때 CPU 스택만을 사용하는데,


중첩함수라고 해도 선언 당시의 변수들을 저장할 수 없다. 왜냐하면 함수가


리턴할 때 CPU 스택에 존재하던 지역변수들을 무조건 삭제하기 때문이다.


하지만 JS의 유효범위체인은 바인딩 스택을 사용하는 것이 아니라, 변수가 저장되어


있는 호출객체를 유효범위 체인의 목록에 추가하는 방식이다. 그리고 가비지컬렉터는


이 호출 객체에 대한 참조가 더이상 없을 때에서야 이 호출 객체를 소멸시킨다.



   


 SHS:  테스트를 위해서 만들어 본 간단한 예)

var str = "전역변수 액세스 로그 -> ";

 

function f(arg1) {

var num1 = 0;  

 

num2 =0;

var functions = [

function g1() { document.write("str : "+str   +"<br> arg1 : "+arg1

+"<br> num1 : "+num1    +"<br> num2 : "+num2   +"<br><br>"); },

function g2() { str = str + num1.toString() ; arg1++; num1++; num2++; },

function g3(i) { functions[i](); } 

];

return functions;

}

 

var shs = f(10);

 

for (var idx =0; idx<3; idx++)  shs[1]() + shs[2](0);

 

num1 = num2 = 0;   // 초기화 해본것.

for (var idx =0; idx<3; idx++)  shs[1]() + shs[2](0);



결과)

str : 전역변수 액세스 로그 -> 0
arg1 : 11
num1 : 1
num2 : 1

str : 전역변수 액세스 로그 -> 01
arg1 : 12
num1 : 2
num2 : 2

str : 전역변수 액세스 로그 -> 012
arg1 : 13
num1 : 3
num2 : 3

str : 전역변수 액세스 로그 -> 0123
arg1 : 14
num1 : 4
num2 : 1

str : 전역변수 액세스 로그 -> 01234
arg1 : 15
num1 : 5
num2 : 2

str : 전역변수 액세스 로그 -> 012345
arg1 : 16
num1 : 6
num2 : 3


 


 


첨부) Steve Yen (스티브옌) breakpoint(중단점) 구현 (클로저의 좋은 활용 예)

function inspect(inspector, title) {

var expression, result;

if ("ignore" in arguments.callee) return;  

 

while(true) {

var message = "";  

if (title) message = title + " | ";   

if (expression) message += " | " + expression + " ===> " + result + " | ";

else expression = "";

message += "Enter an expression to evaluate:";

expression = prompt(message, expression);

    // expression: 사용자가 입력한 조사식

 

if (!expression) return;     

result = inspector(expression); 

}

}

 

// 실제로 팩토리얼 연산함수의 진입점, 루프내부, 이탈직전에 함수를 삽입해서 활용

function factorial(n) {

var inspector = function($) { return eval($); };

inspect(inspector, "Entering factorial()");

var rst = 1;

while(n>1) {

rst = rst * n;

n--;

inspect(inspector, "factorial() loop");

}

inspect(inspector, "Exiting factorial()");

return rst;

}


 



 


 


 


 (N20131226)


 


+ Recent posts