[英]Can this strange behavior with let and var be explained?
以下示例代碼讓我困惑......
"use strict";
var filesToLoad = [ 'fileA','fileB','fileC' ];
var promiseArray = [];
for( let i in filesToLoad ) {
promiseArray.push(
new Promise( function(resolve, reject ) {
setTimeout( function() {
resolve( filesToLoad[i] );
}, Math.random() * 1000 );
})
);
}
Promise.all( promiseArray ).then(function(value) {
console.log(value);
});
我很困惑的原因是我期待控制台上有一個隨機排序的輸出。 但我總是得到以下......
['fileA','fileB','fileC']
這至少讓我感到困惑,但真正讓我摸不着頭腦的是當我改變let i to var i時,我得到以下結果....
['fileC','fileC','fileC']
作為一個最近才試圖完全理解Promise而不是很久以前開始使用let的人,我真的很難過。
進一步閱讀......
在得到很多很棒的答案后,我重構了這個例子來擺脫循環和我 。 對大多數人來說似乎很明顯,但對我來說很有趣......
"use strict";
var filesToLoad = [ 'fileA','fileB','fileC' ];
function start( name ) {
return new Promise( function(resolve, reject ) {
setTimeout( function() {
resolve( name + '_done' );
}, Math.random() * 1000 );
});
}
Promise.all( filesToLoad.map(start) ).then(function(value) {
console.log(value);
});
另外, let
是塊作用域,而var
是函數作用域。
如果使用var i
:
在觸發函數超時后,循環完成並且我被設置為2.所以它通過filesToLoad[2]
解決了所有setTimeout函數。
如果使用let i
:
由於它是塊作用域,當函數被解析時,它會在聲明setTimeOut
時記住i的狀態,因此當它被解析時它使用正確的i值。
關於使用let i的輸出順序。
Promise.all(Iterable<any>|Promise<Iterable<any>> input) -> Promise
給定Iterable(數組是Iterable),或Iterable的承諾,它產生promises(或promises和values的混合),迭代Iterable中的所有值到一個數組中並返回一個在所有的時候滿足的promise數組中的項目已完成。 promise的履行值是一個數組,其在原始數組的各個位置具有履行值。 如果數組中的任何promise都拒絕,則返回的promise將被拒絕並拒絕原因。
因此,無論您的承諾得到解決的順序如何,promise.all的結果將始終具有正確順序的承諾解析價值。
為什么使用let
和var
產生不同的結果:
在使用的原因let
生產超過所需的結果var
是,當使用let
你聲明一個塊范圍的變量 ,使得當所述環移動到另一個迭代的值i
不受影響對於此時的循環的內容。
在for循環的頭文件中定義var
變量並不意味着它僅在for循環的生命周期中存在,因為如果執行以下操作,您將注意到:
for (var i = 0; i < 10; i++) { /*...*/ }
console.log(i); //=> 10
// `i` is already declared and its value will be 10
for (; i < 20; i++) { /*...*/ }
console.log(i); //=> 20
如果使用Array#forEach
,它可以完全避免這個問題,它通過在每次迭代中為回調函數提供下一個值來為您完成filesToLoad[i]
的工作:
filesToLoad.forEach((file) => {
promiseArray.push(
new Promise( function(resolve, reject ) {
setTimeout( function() {
resolve( file );
}, Math.random() * 1000 );
})
);
});
______
使用let
或var
會影響Promise#all
的行為嗎?
在您的示例中, promiseArray
中promiseArray
的位置定義了值添加到結果數組的順序,而不是在解析每個promiseArray
時。 以隨機間隔解析Promises的事實不會在promiseArray
移動已解析值的位置。 你所演示的是Promise#all
生成一個值數組,其位置映射到產生其值的Promise。
有關Promise#all
行為的更多信息,請參閱此答案 :
所有這些意味着輸出嚴格按輸入進行排序,只要輸入是嚴格排序的(例如,數組)。
這是因為var
的范圍不是for
的孩子,它是兄弟姐妹。 所以循環運行一次並設置i = 0
。 然后再運行2次並設置i = 1
然后i = 2
。 在這一切發生之后,超時然后運行並且全部運行resolve函數並傳入resolve( filesToLoad[2] )
。 let
正常工作,因為let i
的值不會被以下循環迭代覆蓋。
簡而言之,超時僅在循環已經運行3次后運行,因此傳遞相同的值。 我用var
創建了一個工作版本的jsfiddle 。
這是let
和var
之間的區別。 您的問題與Promises沒有直接關系(除了它們運行異步的事實)。
TL; DR
var
聲明由解釋器移動到函數的開頭,因此當執行超時時,循環已經在同一個變量上運行到數組的末尾。 let
變量保持在范圍內,因此循環的每次迭代都使用不同的變量。
背景:
Javascript有一個名為hoisting的功能,這意味着變量或函數總是在它的包裝函數的開頭聲明。 如果編碼器沒有,則解釋器移動它。 因此:
var i;
for (i in array)
....
和
for (var i in array)
....
是等價的,即使有一些原因,或者其他編程背景,你會希望i
在第二種情況下僅限於循環。
這就是為什么
alert(i);
...
var i=5;
警告undefined
而不是拋出錯誤。 就口譯員而言 - 有一份聲明。 就像是
var i;
alert(i);
...
i=5;
現在:
ES6 / ES2015引入了let
- 這是我們在上面第二個例子中的第一個假設。
alert(i);
...
let i=5;
實際上會拋出一個錯誤。 到達alert
,沒有i
可以說。
在你的情況下
let
你每次都使用一個新變量進行循環,因為let
只被定義為循環的范圍。 每次迭代都是一個范圍,每個都使用不同的變量(盡管在各自的范圍內都命名為i
)
使用var
,實際上在循環之前聲明了i
var i;
for( i in filesToLoad ) {
promiseArray.push(
new Promise( function(resolve, reject ) {
setTimeout( function() {
resolve( filesToLoad[i] );
}, Math.random() * 1000 );
})
);
}
所以它在每次迭代(或者在每個范圍內)中都是相同的變量,這意味着循環在返回任何超時之前將最后一項分配給i
。 這就是為什么結果是最后一項的3倍。
這實際上是兩個不同的,無關的問題。
您的第一個觀察結果是輸出是按原始順序而不是隨機順序,這是由Promise.all
的行為Promise.all
。 Promise.all
返回一個新的promise,當所有子promise都以原始順序解析時,該promise將解析為數組。 你可以把它想象成Array.map
,但是對於promises。
promise的履行值是一個數組,其在原始數組的各個位置具有履行值。
你的第二個問題是,如何更改let
to var
更改輸出以具有所有相同的值,是由於數組作用域。 簡單地說,具有限定的可變var
存在的外部 for
循環范圍。 這意味着在每次迭代時,您實際上都在修改預先存在的變量的值,而不是創建新變量。 由於Promise中的代碼稍后執行,因此變量將具有上一次循環迭代的值。
let
創建一個內部存在一個變量for
迭代。 這意味着,稍后在Promise中,您將訪問在該迭代范圍中定義的變量,該變量不會被下一個循環迭代覆蓋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.