簡體   English   中英

可以用let和var解釋這種奇怪的行為嗎?

[英]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);
});

這是因為關閉。 herehere閱讀它。

另外, 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的結果將始終具有正確順序的承諾解析價值。

為什么使用letvar產生不同的結果:

在使用的原因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 );
    })
  );
});

______

使用letvar會影響Promise#all的行為嗎?

在您的示例中, promiseArraypromiseArray的位置定義了值添加到結果數組的順序,而不是在解析每個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

這是letvar之間的區別。 您的問題與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。

Bluebird(JS promise庫)文檔中所述

promise的履行值是一個數組,其在原始數組的各個位置具有履行值。

你的第二個問題是,如何更改let to var更改輸出以具有所有相同的值,是由於數組作用域。 簡單地說,具有限定的可變var存在的外部 for循環范圍。 這意味着在每次迭代時,您實際上都在修改預先存在的變量的值,而不是創建新變量。 由於Promise中的代碼稍后執行,因此變量將具有上一次循環迭代的值。

let創建一個內部存在一個變量for迭代。 這意味着,稍后在Promise中,您將訪問在該迭代范圍中定義的變量,該變量不會被下一個循環迭代覆蓋。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM