简体   繁体   English

无法理解 JS 中这个 for 循环的输出

[英]Not able to understand the output of this for loop in JS

I have understood why the output of this code should be 3 3 3 .我已经理解为什么这段代码的输出应该是3 3 3

 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); }

I am not able to understand, though, why the output of this code is 0 1 2 .但是,我无法理解为什么这段代码的输出是0 1 2

 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); }

I want more clarity with the output of the second for loop.我希望第二个for循环的输出更清楚。

There is a difference of the scope with let and var , which causes them to behave differently. letvar作用域不同,这导致它们的行为不同。

Here is a quote from other Stack Overflow answer about the differences between var and let .这是其他Stack Overflow 答案中关于varlet之间差异的引用。

The main difference is scoping rules.主要区别在于范围规则。 Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).var关键字声明的变量的作用域是直接函数体(因此是函数作用域),而let变量的作用域是由{ }表示的直接封闭块(因此是块作用域)。

So, in summary, var is referring to the same variable address.因此,总而言之, var指的是相同的变量地址。 But, as let is blocked scope (according to the quote above), every callback in setTimeout() will make i have a different value then the previous one.但是,由于let是阻塞范围(根据上面的引用), setTimeout()中的每个回调都会使i具有与前一个不同的值。


An experiment that is possible is to make let behave like var .一个可能的实验是让let表现得像var To make let behave like var , you can use (and run) the code below.要让let表现得像var ,您可以使用(并运行)下面的代码。

To see how let is behaving like var , read ahead about var hoisting!要了解let的行为如何像var ,请提前阅读var提升!

 let i; for (i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); }

As we moved let to the main scope, it behaves like var (as var is positioned at the top of the file with a value of undefined at runtime).当我们将let移至主作用域时,它的行为类似于var (因为var位于文件顶部,运行时值为undefined )。

This makes the let variable behave like var , making the output of the for loop the same as if the let variable was var .这使得let变量的行为类似于var ,使得for循环的输出与let变量为var的输出相同。

Thanks to Nick Vu for the experiment!感谢Nick Vu的实验!


Here is a quick overview of var hoisting below.下面是对var提升的快速概述。

The actual code (shown below):实际代码(如下所示):

web = "stackoverflow.com";
var web;

Is understood as:可以理解为:

var web;
web = "stackoverflow.com";

Basically, defining a variable with var anywhere will always result it being at the top of the file, resulting in the weird behaviour with the for loop.基本上,在任何地方用var定义变量总是会导致它位于文件的顶部,从而导致for循环出现奇怪的行为。

This is why many people prefer let over var ;这就是为什么许多人更喜欢let而不是var的原因; because of how confusing var hoisting can be!因为var提升是多么令人困惑!


Always use let , unless var is absolutely needed!始终使用let ,除非绝对需要var

The first loop var i is hoisted and always referred to only 1 variable during that loop, setTimeout will postpone your console.log with call stack , so that's why the value is 3 all.第一个循环var i提升并且在该循环期间始终仅引用一个变量, setTimeout将使用调用堆栈推迟您的console.log ,这就是为什么值为3 all 的原因。 Here is how var i is constructed with hoisting under the hood.以下是如何通过在引擎盖下提升来构建var i

 var i; //hoisted //`var` in `for` gets removed for (i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); }

But let i is different, i value will be initialized under that block scope in iteration each time, so that's why the result is 0 1 2 .但是let i不同,每次迭代中i值都会在该块范围内初始化,所以结果是0 1 2

For an experiment, to make let has a similar result as the var loop.对于一个实验, make let的结果与var循环类似。

 let i; //move `i` to the upper block scope for (i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); //all logs will share the same `i` }

In case of var you are always referring to the same variable address, because var is function scoped.var的情况下,您总是引用相同的变量地址,因为var是函数范围的。

let is block scoped. let是块作用域。 For every callback in setTimeout you have a different value of i , since it is a different block.对于 setTimeout 中的每个回调,您都有不同的i值,因为它是不同的块。

Read

This is too lengthy but I make sure that you get it,这太长了,但我确保你明白,

In the first for loop , variable is declared using 'var' keyword.在第一个 for loop中,使用'var'关键字声明变量。 Variables declared using 'var' keyword are function scoped.使用 'var' 关键字声明的变量是函数范围的。 As our first 'for' loop is not enclosed within any function, the variable is global , and the value of this global variable was incremented at each iteration of the first for loop.由于我们的第一个“for”循环没有enclosed在任何函数中,所以变量是global ,并且这个global variable的值在第一个 for 循环的每次迭代中incremented At the end of the first for loop, the variable 'i' will have the value of 3 as the value is overwritten at each iteration of the first for loop.在第一个 for 循环结束时,变量'i'的值为3 ,因为该值在第一个 for 循环的每次迭代中都会被overwritten So, at the time of execution of the first callback function from the 'callback queue' , the value of 'i' will be 3.因此,在执行'callback queue'中的第一个回调函数时,“i”的值将是 3。

So, the first callback function will print numeric value 3 at console.因此,第一个回调函数将在控制台打印数值 3。 The second and third callback function will also print the same numeric value 3 as it points the same variable.第二个和第三个回调函数也将打印相同的数值 3,因为它指向同一个变量。

But in the case of second for loop, we've used 'let' keyword to declare the variable.但是在第二个for循环的情况下,我们使用'let'关键字来声明变量。 Variables declared using 'let' keyword are block scoped .使用 'let' 关键字声明的变量是block scoped的。 A block is nothing but a pair of curly braces.块只不过是一对花括号。 So, in second for loop, a new value is used for each iteration.因此,在第二个 for 循环中,每次迭代都会使用一个新值。 So, the last 3 callback functions will have the numeric values 0, 1, 2 respectively for the variable 'i' and hence would print 0, 1 and 2.因此,最后 3 个回调函数将分别具有变量“i”的数值 0、1、2,因此将打印 0、1 和 2。

We can make the callback function to use the new variable value every time using 'var' keyword itself.我们可以使回调函数每次使用'var'关键字本身时都使用新的变量值。 As variables in javascript are function scoped (unless you use 'let' or 'const' keyword), you can call a function by passing the iteration variable.由于 javascript 中的变量是函数范围的(除非您使用'let''const'关键字),因此您可以通过传递迭代变量来调用函数。 This will create a new variable every time.这将每次创建一个新变量。

for(var i=0 ;i <3 ; i++){
     setTimeOut(()=>console.log(i),1);
}

Js engine will examine the first for loop in the following steps : Js 引擎将按以下步骤检查第一个 for 循环:

  1. i will be hoisted, to look like this :我将被吊起,看起来像这样:
    var i;
    for(i = 0 ;i < 3 ; i++){
       setTimeOut(()=>console.log(i),1);
    }
  1. After the loop get's over , it will run setTimeOut for 3 times which will print value of i which is now 3循环结束后,它将运行 setTimeOut 3 次,这将打印i的值,现在是 3
{ 
   i = 3 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 3
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 3
   setTimeOut(()=>console.log(i,1);
}
for(let i=0 ;i <3 ; i++){
     setTimeOut(()=>console.log(i),1);
}

Js engine will examine the second for loop like this : Js 引擎将像这样检查第二个 for 循环:

  1. i will be hoisted, to look like this :我将被吊起,看起来像这样:
    for(let i=0 ;i < 3 ; i++){
       setTimeOut(()=>console.log(i),1);
    }

for the first iteration , the value of i would be 0 , and for the second iteration the value of i would be 1 and for the third it's value would be 2 .对于第一次迭代, i 的值为 0 ,第二次迭代 i 的值为 1 ,第三次迭代的值为 2 。

Imagine , it as three different blocks of scope like this :想象一下,它是三个不同的范围块,如下所示:

{ 
   i = 0 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 1 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 2
   setTimeOut(()=>console.log(i,1);
}

First loop第一个循环

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

Second loop第二循环

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

Solution of first loop第一个循环的解决方案
Because when we declare the variable using var it will be either in global scope(when declare in global execution context) or in function scope(when declare in function execution context).So as we know in the first loop we declare the variable in the global execution context so it will have global scope.因为当我们使用 var 声明变量时,它将在全局范围内(在全局执行上下文中声明时)或在函数范围内(在函数执行上下文中声明时)。所以我们知道,在第一个循环中,我们在全局执行上下文,因此它将具有全局范围。 When setTimeout encounters in first loop it will give the setTimeout handler in the API's.当 setTimeout 在第一个循环中遇到时,它将在 API 中提供 setTimeout 处理程序。 But as we know js and time never waits for anything so js will go to next iterations and js engine will execute the for loop very fastly and when the 1s of setTimeout happens then it will push the handler functions in execution stack and now the value of i(global scope) is 3. Here the main thing is that the same i is bound to every function of setTimeout because of the concept of closures(if beginner then to understand think that i is a global variable).但是我们知道 js 和 time 永远不会等待任何东西,所以 js 将进入下一次迭代,并且 js 引擎将非常快速地执行 for 循环,当 setTimeout 的 1 发生时,它会将处理函数推送到执行堆栈中,现在的值i(global scope) 是 3。这里主要是由于闭包的概念,相同的 i 绑定到 setTimeout 的每个函数(如果初学者理解认为 i 是全局变量)。 So console.log(i) will print 3 in each handler function of setTimeout.所以console.log(i) 将在setTimeout 的每个处理函数中打印3。

Solution of second loop第二个循环的解决方案
Till ES6 developers feels very difficult because there is no block scope in javascript like other languages.直到 ES6 开发人员都觉得非常困难,因为 javascript 中没有像其他语言那样的块作用域。 So in ES6, keyword let is introduced which introduces the concept of block scope.所以在 ES6 中,引入了关键字 let,它引入了块作用域的概念。 Means the scope of variables declared using let will be only in that block(variable enclosed in the first curly braces).意味着使用 let 声明的变量的范围将仅在该块中(变量包含在第一个花括号中)。 In some cases and generally in loops it makes more sense that variable has block scope.在某些情况下,通常在循环中,变量具有块范围更有意义。
Now , the variable i declared in second loop will have block scope of for loop.现在,我在第二个循环中声明的变量将具有 for 循环的块范围。 It will be available only in the one round of for loop.它将仅在一轮 for 循环中可用。 Now, the i is bound to every iteration of for loop.现在, i 绑定到 for 循环的每次迭代。 So, the new iteration will have seperate set of i here.所以,新的迭代将在这里有单独的 i 集。
So, in second loop there will be 3 i which will bind to every iteration of for loop.因此,在第二个循环中,将有 3 个 i 绑定到 for 循环的每次迭代。 Now, when first iteration of for loop executes the value of i is 0 and it is bound to this iteration and setTimeout statement runs it is stored in background by javascript and will execute the function when 1s will be completed.现在,当 for 循环的第一次迭代执行时 i 的值为 0 并且它绑定到此迭代并运行 setTimeout 语句,它由 javascript 存储在后台,并在 1s 完成时执行该函数。 But js waits for nobody.但是js不等人。 So next iterations of for loop will occur and in second iteration i value will be 1 bound to this iteration and so on.因此 for 循环的下一次迭代将发生,并且在第二次迭代中 i 值将 1 绑定到此迭代,依此类推。
When 1s happens the function of setTimeout will pushed to execution stack and each function have its i because for every iteration of for loop it has its own i which is bound to it(Here the concept of closures also come into picture).当 1s 发生时,setTimeout 的函数将被推送到执行堆栈,并且每个函数都有它的 i,因为对于 for 循环的每次迭代,它都有自己的 i 绑定到它(这里也出现了闭包的概念)。
So now every function execute of setTimeout and it will print 0,1 and 2.所以现在 setTimeout 的每个函数都会执行,它会打印 0,1 和 2。

Some more info更多信息
As told upward that i is bound to every iteration.正如上面所说的, i 绑定到每次迭代。 So, when one iteration completed it will destroy.因此,当一个迭代完成时,它将破坏。 Then in next iteration how the for loop increemented 1 to the previous i.然后在下一次迭代中,for 循环如何将 1 增加到前一个 i。 So, here the javascript actually uses var in background to maintain the for loop.因此,这里的 javascript 实际上在后台使用 var 来维护 for 循环。
Any doubt or suggestion, make a comment有任何疑问或建议,发表评论

Because of the event queue in JavaScript, the setTimeout callback function is called after the loop has been executed.由于 JavaScript 中的事件队列,setTimeout 回调函数在循环执行后被调用。 Since the variable i in the first loop was declared using the var keyword, this value was global.由于第一个循环中的变量 i 是使用 var 关键字声明的,因此该值是全局的。 During the loop, we incremented the value of i by 1 each time, using the unary operator ++.在循环期间,我们使用一元运算符 ++ 每次将 i 的值增加 1。 By the time the setTimeout callback function was invoked, i was equal to 3 in the first example.在调用 setTimeout 回调函数时,在第一个示例中 i 等于 3。

In the second loop, the variable i was declared using the let keyword: variables declared with the let (and const) keyword are block-scoped (a block is anything between { }).在第二个循环中,变量 i 使用 let 关键字声明:使用 let(和 const)关键字声明的变量是块范围的(块是 { } 之间的任何内容)。 During each iteration, i will have a new value, and each value is scoped inside the loop.在每次迭代期间,我都会有一个新值,并且每个值都在循环内。

简单地说, setTimeout 导致日志稍后运行,并且使用var有一个i实例,而使用let每个循环都有一个新实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM