2010-07-17 6 views
23

내가 폐쇄에 모질라 개발자의 사이트를 읽고 있어요, 나는 일반적인 실수에 대한 자신의 예에서 발견, 그들은이 코드를했다 그들은 onFocus 이벤트에 대해 onFocus 이벤트에 할당 된 모든 익명 함수가 'item'변수를 감싸는 클로저를 가지고 있기 때문에 코드는 마지막 항목에 대한 도움말 만 표시한다고 말했습니다. 이는 JavaScript 변수에 블록 범위. 해결책은 대신 'let item = ...'을 사용하는 것이 었습니다. 블록 범위가 있기 때문입니다.자바 스크립트 폐쇄 변수 범위 질문

그러나 왜 내가 'var item'을 for 루프 바로 위에 선언 할 수 없는지 궁금한가요? 그런 다음 setupHelp()의 범위를 지정하고 각 반복에 다른 값을 할당하면 클로저의 현재 값으로 캡처됩니다 ... 맞습니까?

+2

자바 스크립트에'let'이 있습니까? look up ... – Kobi

+1

@Kobi : Mozilla에만 국한된 JavaScript 확장 기능입니다. [this] (https://developer.mozilla.org/en/new_in_javascript_1.7)를 참조하십시오. –

+0

Mozilla에만 해당되는 경우 사용하지 말아야합니까? – Nick

답변

26

그 이유는 item.help이 평가 되었기 때문에 루프가 완전히 완료되었을 것입니다. 대신 닫음을 사용하여이 작업을 수행 할 수 있습니다.

for (var i = 0; i < helpText.length; i++) { 
    document.getElementById(helpText[i].id).onfocus = function(item) { 
      return function() {showHelp(item.help);}; 
     }(helpText[i]); 
} 

JavaScript에는 차단 범위가 없지만 기능 범위가 있습니다. 클로저를 만들면 helpText[i]에 대한 참조가 영구적으로 캡처됩니다.

+3

이것은 표준 수용 솔루션으로, 이벤트 처리 함수를 반환하는 함수를 만들고 변수를 범위 지정합니다. 추적 할 필요가있다. 흥미로운 측면 노트, jQuery의 ['$. each()'] (http://api.jquery.com/jQuery.each)는 인덱스와 항목을 지정한 함수로 전달할 때이 작업을 자체적으로 수행합니다. 따라서 루프의 내부 범위를 쉽게 지정할 수 있습니다. – gnarf

1

for 루프 외부에서 선언 된 경우에도 각각의 익명 함수는 여전히 동일한 변수를 참조하므로 루프 이후에는 모든 항목이 여전히 item의 최종 값을 가리 킵니다.

2

새 범위는 function 블록 (및 with이지만 생성자는 사용하지 않음)으로 생성되었습니다. for 같은 루프는 새 범위를 만들지 않습니다.

그래서 루프 밖에서 변수를 선언하더라도 똑같은 문제가 발생합니다.

+2

'with'는 새로운 어휘 환경을 생성하지 않습니다. 예를 들어'with' 블록 안에 변수를 선언하면 변수가 부모 범위에 바인딩됩니다 (* hoisted *가됩니다) . 'with '는 범위 체인 앞에서 객체를 소개하기 만합니다. [more info] (http://stackoverflow.com/questions/2742819/) – CMS

20

클로저는 해당 기능의 기능 및 범위가 지정된 환경입니다.

이 경우 Java에서 범위를 구현하는 방법을 이해하는 데 도움이됩니다. 사실, 그것은 일련의 중첩 된 사전들입니다.

var global1 = "foo"; 

function myFunc() { 
    var x = 0; 
    global1 = "bar"; 
} 

myFunc(); 

이 프로그램이 실행을 시작하면, 당신은 하나의 범위 사전을 가지고, 거기에 정의 된 여러 가지가있을 수 있습니다 글로벌 사전, :

{ global1: "foo", myFunc:<function code> } 

당신이 MYFUNC를 호출 할 말이 코드를 고려 이것은 로컬 변수 x를가집니다. 이 함수의 실행을 위해 새 범위가 만들어집니다. 함수의 로컬 범위는 다음과 같습니다.

{ x: 0 } 

또한 상위 범위에 대한 참조를 포함합니다. 따라서 함수의 전체 범위는 다음과 같습니다.

{ x: 0, parentScope: { global1: "foo", myFunc:<function code> } } 

이렇게하면 myFunc에서 global1을 수정할 수 있습니다. Javascript에서는 변수에 값을 할당하려고 할 때마다 변수 이름의 로컬 범위를 먼저 확인합니다. 발견되지 않으면 변수가 발견 될 때까지 parentScope 및 해당 범위의 parentScope 등을 검사합니다.

클로저는 말 그대로 함수의 범위 (부모 범위에 대한 포인터를 포함하는 등)에 대한 포인터를 더한 함수입니다. for 루프가 실행이 완료된 후에 그래서, 당신의 예에서, 범위는 다음과 같습니다

setupHelpScope = { 
    helpText:<...>, 
    i: 3, 
    item: {'id': 'age', 'help': 'Your age (you must be over 16)'}, 
    parentScope: <...> 
} 

이 하나의 범위 객체를 가리 킵니다 만들 때마다 폐쇄. 우리가 만든 모든 폐쇄를 나열한다면, 그것은 다음과 같이 보일 것입니다 :

[anonymousFunction1, setupHelpScope] 
[anonymousFunction2, setupHelpScope] 
[anonymousFunction3, setupHelpScope] 

이러한 기능 중 하나가, 그것이 전달하는 범위 개체를 사용하여 실행할 때 -이 경우는, 동일한 범위의 각 기능에 대한 객체! 각각은 동일한 item 변수를보고 동일한 값을 보게되며 이는 for 루프에 의해 설정된 마지막 값입니다.

질문에 대답하려면 var item을 루프에 넣거나 그 안에 넣을지는 중요하지 않습니다. for 루프는 자체 범위를 만들지 않으므로 item은 현재 함수의 범위 사전 인 setupHelpScope에 저장됩니다. for 루프 내부에서 생성 된 인클로저는 항상 setupHelpScope을 가리 킵니다.

몇 가지 중요한 사항 : 자바 스크립트, for 루프가 자신의 범위를하지 않아도 때문에이 문제가 발생

  • - 그들은 단지 바깥 쪽 함수의 범위를 사용합니다. if, while, switch 등의 경우에도 마찬가지입니다. 반면 C# 인 경우 각 루프에 대해 새 범위 개체가 만들어지고 각 클로저에는 고유 한 범위에 대한 포인터가 포함됩니다.
  • anonymousFunction1이 해당 범위의 변수를 수정하는 경우 다른 익명 함수의 해당 변수를 수정한다는 점에 유의하십시오. 이것은 몇몇 기이 한 상호 작용을 일으킬 수 있습니다.
  • 범위는 사용자가 프로그래밍하는 것과 같은 개체입니다. 구체적으로 사전입니다. JS 가상 머신은 가비지 컬렉터를 사용하여 메모리에서 삭제 작업을 관리합니다. 이러한 이유 때문에 클로저를 과도하게 사용하면 실제 메모리가 부풀어 오를 수 있습니다. 클로저에는 스코프 객체에 대한 포인터가 포함되어 있으므로 (이는 상위 범위 객체에 대한 포인터를 포함하고 있음) 전체 스코프 체인을 가비지 수집 할 수 없으므로 메모리에 고수해야합니다.

추가 읽기 :

+2

이것은 자바 스크립트 범위 지정을 설명하는 좋은 방법입니다. 문제에 대한 해결책을 설명하십시오. 변수의 사본을 그대로 저장하려면, 함수를 반환하는 함수를 만들어서 새로운 범위를 생성하면됩니다 :'(function (item) {return function() {...}}) (item);'범위가 지정된'item'에 접근 할 수 있습니다. 다음과 같이 코드에서 더 일찍 함수를 정의 할 수도 있습니다 :'function genHelpFocus (item) {return function() {showHelp (item.html); }}'를 사용하여 범위 유출을 방지합니다. – gnarf

+0

그것은 내가들은 가장 분명한 설명이다. 감사 – Adam

3

5 년 오래 ...당신은 (그것을 또는 가까운) 한 라이너를 원하는 경우

// Function only exists once in memory 
function doOnFocus() { 
    // ...but you make the assumption that it'll be called with 
    // the right "this" (context) 
    var item = helpText[this.index]; 
    showHelp(item.help); 
}; 

for (var i = 0; i < helpText.length; i++) { 
    // Create the special context that the callback function 
    // will be called with. This context will have an attr "i" 
    // whose value is the current value of "i" in this loop in 
    // each iteration 
    var context = {index: i}; 

    document.getElementById(helpText[i].id).onfocus = doOnFocus.bind(context); 
} 

:하지만 당신은 또한 콜백 함수에 다른/특별한 범위 단지 바인드은 각 요소에 할당 할 수

// Kind of messy... 
for (var i = 0; i < helpText.length; i++) { 
    document.getElementById(helpText[i].id).onfocus = function(){ 
     showHelp(helpText[this.index].help); 
    }.bind({index: i}); 
} 

또는 더 나은 점은 범위 문제를 해결하는 EcmaScript 5.1의 array.prototype.forEach을 사용할 수 있다는 것입니다.

helpText.forEach(function(help){ 
    document.getElementById(help.id).onfocus = function(){ 
     showHelp(help); 
    }; 
});