2015-02-24 50 views
0

假設我們有一個簡單的對象列表:在這裏創建閉包有什麼選擇?

var things = [ 
    { id: 1, name: 'one' }, 
    { id: 2, name: 'two' }, 
    { id: 3, name: 'three' } 
]; 

而我們需要通過這些對象進行迭代並註冊爲後來的一些事件參數。天真的方法分享所有的回調之間對最後項目各大火相同的對象引用,所以:

for (var i = 0; i < things.length; i++) { 
    var o = things[i]; 
    setTimeout(function() { doSomethingWith(o); }, i * 1000); 
} 

一個典型的解決方案是創建一個封閉,限制了我們的對象引用的範圍:

for (var i = 0; i < things.length; i++) { 
    (function() { 
    var o = things[i]; 
    setTimeout(function() { doSomethingWith(o); }, i * 1000); 
    })(); 
} 

如果我們不針對IE < 9,我們可以依靠.forEach()

things.forEach(function(o, i) { 
    var o = things[i]; 
    setTimeout(function() { doSomethingWith(o); }, i * 1000); 
}); 

但是,無論如何,我們最終在傳遞一個匿名函數forEach()時創建了一種閉包。

有沒有一種方法可以在沒有閉包或函數引用的情況下完成此操作?

潛在的問題有三個方面:

  1. 同樣重要的是:傳入setTimeout()(或任何可能)讓你(我)覺得就像你正在創建的函數引用封閉。所以,我傾向於忘記外部封閉。
  2. 更重要的是:附加函數/閉包聲明鼓勵「箭頭代碼」。對於複雜的操作,複雜操作的代碼可讀性會隨着代碼遷移到屏幕上而迅速惡化......除非應用程序或組織風格指南指定關閉的換行符+縮進,否則這可能在IE> 9中由.forEach()解決。
  3. 大多數重要的是:我很確定有一個簡單的方法來處理這個問題。我覺得現在無法想象它是愚蠢的。

也許更好的方法來問這是:在我們開始強制創建關閉之前,我們都做了什麼惡魔?

回答

1

我不認爲在這裏使用閉包有任何問題。它們是JavaScript中的天然工具,對於本地狀態的異步回調而言非常必要 - 因爲我們希望避免全局狀態。

如果你很在乎縮進,你可以把範圍,提供關於同一行循環IEFE:

for (var i = 0; i < things.length; i++) (function() { 
    var o = things[i]; 
    setTimeout(function() { doSomethingWith(o); }, i * 1000); 
}()); 

否則,您已經使用了forEach完美的罰款。請注意,您不需要在代碼中關注IE < = 8,因爲forEach如果您想要支持它,它是平凡的。

和當然,ES6會給我們let語句用新語法解決這個very common problem - 你需要,雖然使用6to5-transpiler:

for (let i = 0; i < things.length; i++) { 
// ^^^ 
    var o = things[i]; 
    setTimeout(function() { doSomethingWith(o); }, i * 1000); 
} 

如果你想有一個非常明確的代碼組織,使封明確:

function makeDoer(thing) { 
    return function() { doSomethingWith(thing); }; 
} 
for (var i = 0; i < things.length; i++) { 
    setTimeout(makeDoer(things[i]), i*1000); 
} 

什麼魔鬼爲什麼我們都這樣做之前,我們都開始強制關閉創造?

我們使用了全局狀態,並以不同的方式解決了我們的問題。例如,您的情況將通過半遞歸函數解決好得多:

var i = 0; 
function next() { 
    if (i < things.length) { 
     doSomethingWith(things[i++]); 
     setTimeout(next, 1000); 
    } 
} 
next(); 
+0

*我們使用全局狀態* ...我認爲這就是我正在尋找的。我無法圍繞我們*用來做什麼來管理這種類型的東西,爲什麼我停下來了。但是,我很確定就是這樣。我們擰了很多全球性的東西。 – svidgen 2015-02-24 19:20:08

1

我弄明白了,有兩種不同的方法。 這第一個,你綁定參數該方法的調用。 它將函數內部的參數[i]克隆並用作參數。

for (var i = 0; i < things.length; i++) { 
    var o = things[i]; 
    setTimeout(doSomethingWith.bind(null, things[i]), i * 1000); 
} 

,並在第二個方式,以毫秒爲參數的時間後

的setTimeout,接受函數的參數,你會打電話,這也爲它定義的瞬間值複製,所以變量值可以在setTimeout和setTimeout之後更改,以保證將正確的值作爲參數傳遞。

for (var i = 0; i < things.length; i++) { 
var o = things[i]; 
setTimeout(function(param) { doSomethingWith(param); }, i * 1000, o); 
} 

希望它有幫助!

+0

+1,但請注意綁定函數也可以被視爲關閉,並且所有瀏覽器都不支持其他'setTimeout'參數 – Bergi 2015-02-24 18:30:42