2013-10-30 193 views
24

最近,我發現自己需要創建一組函數。這些函數使用XML文檔中的值,並且我正在使用for循環通過適當的節點運行。但是,這樣做後,我發現只有XML表格的最後一個節點(對應於for循環的最後一次運行)曾被數組中的所有函數使用。Javascript:在For循環中創建函數

下面是展示這樣一個例子:

var numArr = []; 
var funArr = []; 
for(var i = 0; i < 10; ++i){ 
    numArr[numArr.length] = i; 
    funArr[funArr.length] = function(){ return i; }; 
} 

window.alert("Num: " + numArr[5] + "\nFun: " + funArr[5]()); 

輸出是民:5和樂趣:10

經研究,我發現了一個a segment of code that works,但我很努力準確地理解它爲什麼有效。我轉載在這裏用我的例子:

var funArr2 = []; 
for(var i = 0; i < 10; ++i) 
    funArr2[funArr2.length] = (function(i){ return function(){ return i;}})(i); 

window.alert("Fun 2: " + funArr2[5]()); 

我知道它與範圍有關,但乍一看似乎並不像它會從我的幼稚的做法不同的執行任何。我在Javascript中是一個初學者,所以如果我可能會問,爲什麼使用這個函數返回函數技術繞過了範圍問題?另外,爲什麼(i)包含在最後?

非常感謝您提前。

回答

20

第二種方法是更清晰一點,如果你使用不掩蓋循環變量名稱參數名稱:

funArr[funArr.length] = (function(val) { return function(){ return val; }})(i); 

與您現有的代碼的問題是,每個函數是一個closure,他們都參考相同的變量i。當每個函數運行時,它在函數運行時返回值爲i(這將比循環的極限值多一個)。

更明確的方式是寫一個單獨的函數,返回要關閉:

var numArr = []; 
var funArr = []; 
for(var i = 0; i < 10; ++i){ 
    numArr[numArr.length] = i; 
    funArr[funArr.length] = getFun(i); 
} 

function getFun(val) { 
    return function() { return val; }; 
} 

注意,這基本上是做同樣的事情,在我的答案代碼的第一行:調用函數返回一個函數並將i的值作爲參數傳遞。主要優點是清晰度。

編輯:現在的EcmaScript 6支持幾乎無處不在(很抱歉,IE用戶),你可以用一個簡單的方法獲得由—使用let關鍵字來代替var的循環變量:

var numArr = []; 
var funArr = []; 
for(let i = 0; i < 10; ++i){ 
    numArr[numArr.length] = i; 
    funArr[funArr.length] = function(){ return i; }; 
} 

隨着那個小小的變化,每個funArr元素都是一個閉合邊界,在每個循環迭代中都有一個不同的 i對象。有關let的更多信息,請參閱this Mozilla Hacks post from 2015。 (如果您的目標環境不支持let,請堅持使用我之前編寫的內容,或者在使用前通過編譯器運行該編譯器。

+0

我是JS中的新手,非常感謝,在您發佈後,我學會了如何動態創建函數(回調函數)! – mineroot

+0

非常有幫助。剛剛用這個來解決我的問題。 http://stackoverflow.com/questions/41254076/preventing-lazy-evaluation-in-function-called-by-fabric-js/41254197#41254197非常感謝。 –

5

讓我們調查一下代碼做了些什麼,並指定了虛函數名稱:

(function outer(i) { 
    return function inner() { 
     return i; 
    } 
})(i); 

這裏,outer接收一個參數i。JavaScript採用函數範圍,這意味着每個變量只存在於其定義的函數中。i此處在outer中定義,因此存在於outer(以及任何範圍內)。

inner包含對變量i的引用。 (請注意,它不會而不是重新定義i作爲參數或與var關鍵字!)JavaScript的範圍規則聲明這樣的引用應該綁定到第一個封閉範圍,這是outer的範圍。因此,inner中的i指的是與outer內相同的i

最後,在定義函數outer後,我們立即調用它,並將值i(它是一個單獨的變量,在最外層範圍中定義)傳遞給它。值i包含在outer之內,其值現在不能由最外層範圍內的任何代碼進行更改。因此,當for循環中最外面的i增加時,outer中的i保持相同的值。記住我們實際上已經創建了一些匿名函數,每個函數都有自己的範圍和參數值,但希望清楚這些匿名函數中的每個函數如何保留其自身的值i

最後,完整性,讓我們來看看與原來的代碼發生了什麼:

for(var i = 0; i < 10; ++i){ 
    numArr[numArr.length] = i; 
    funArr[funArr.length] = function(){ return i; }; 
} 

在這裏,我們可以看到匿名函數包含對最i的參考。隨着該值的變化,它將反映在匿名函數中,該函數不保留任何形式的值的副本。因此,由於i == 10在我們去的所有這些函數的最外層範圍中,並且每個函數都會返回值10

4

我建議你拿起一本像這樣的書籍:權威指南以獲得對JavaScript的更深入的理解,這樣你就可以避免這樣的常見陷阱。

這個答案還提供專門就關閉一個體面的解釋:

How do JavaScript closures work?

當你調用

function() { return i; } 

功能實際上是做父母的呼叫對象的變量查找(範圍),這是我定義的地方。在這種情況下,i定義爲10,因此這些功能的每一個一個將返回10.原因這個作品

(function(i){ return function(){ return i;}})(i); 

是,通過立即調用匿名函數,一個新的呼叫對象中創建其中電流i被定義。因此,當你調用嵌套函數時,該函數引用匿名函數的調用對象(它定義了調用時傳遞給它的任何值),而不是我最初定義的範圍(它仍然是10) 。

+0

非常有幫助。謝謝! – nick