遇到一個很有意思的BUG,是關於閉包的使用,大概簡化后類似於以下代碼:
for ( var i:int = 0; i < 2; i++)
{
arr[i] = function():void
{
trace(i);
}
}
for each ( var f:Function in arr)
{
f();
}
猜一下上面代碼的輸出是什么?答案是:2 2
而事實上我期望的結果是:0 1
可為什么結果和我的期望相差這么大呢?
再看一段Lua代碼:
for i= 0, 1 do
arr[#arr+ 1] = function()
print(i)
end
end
for _,v in ipairs(arr) do
v()
end
再猜一下,上面的答案?
這回的結果正好是我期望的結果,輸出是:0 1
上面兩段代碼應該差不多是等價的,不過AS3中卻並沒有輸出我期望的結果。
從我個人的期望上來說,我所理解的閉包,應該能在我定義function的時候就把所有的上下文保存好,這樣也就能在調用的時候正確的取到upvalue,也就能正常的輸出了。
對於這點,Lua的運行結果正是我需要的,可無奈的是我的主要開發語言是AS3,似乎AS3的閉包實現機制有些問題?循環語句中的 i 是一個引用,而再次調用 f 的時候,拿到的 i 還是原來的那個。
查了查資料,事實上早有人遇到過我類似的問題,傳送門:http://stackoverflow.com/questions/422784/how-to-fix-closure-problem-in-actionscript-3-as3
所以,也就有了以下的解決方法,看代碼:
for (var i:int = 0; i < 2; i++)
{
arr.push(test(i));
function test(i:int):Function
{
return function():void
{
trace(i);
}
}
}
for each (var f:Function in arr)
{
f();
}
這里做了件很巧妙的事情, test 方法返回了一個 function ,test 方法本身接受一個參數,而函數在傳參過程中,類似 i:int 這樣的基礎類型數據是傳值的,也就是說會拷貝一份 i 的復本出來,相同的數據類型還有其他的包括 Boolean 、Number 、String 、uint。
所以當調用 test 方法的時候實際上是保存了一個 i 的復本。然后 arr 再把 test 返回的方法塞進去,因此在調用 arr 中的方法的時候實際上調用的是 test 返回的那個匿名方法。
因此上面的輸出就是我們期望的輸出:0 1
這算不算一個BUG呢,不完整的閉包嗎?