繁体   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