Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day15 - let为什么能够解决循环陷阱 #58

Open
su37josephxia opened this issue Jan 11, 2022 · 26 comments
Open

Day15 - let为什么能够解决循环陷阱 #58

su37josephxia opened this issue Jan 11, 2022 · 26 comments

Comments

@su37josephxia
Copy link
Owner

su37josephxia commented Jan 11, 2022

var有陷阱

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

let无陷阱

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6
@QbjGKNick
Copy link

当var声明变量时,不存在块级作用域,a数组内每个函数中的i都是向上查询作用域a,所以结果都是10

var a = [];
for (var i = 0; i < 10; i++) {
  // 作用域a
  a[i] = function () {
   // 作用域b
    console.log(i);
  };
}
a[6](); // 10

当let声明变量时,由于存在块级作用域,每次循环i都是一个新的变量,都会创建一个新的词法作用域,所以最后是6,我们可以通过babel把let代码编译成es5代码,如下所示:

"use strict";

var a = [];

var _loop = function _loop(i) {
  a[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 10; i++) {
  _loop(i);
}
a[6]();

@ruixue0702
Copy link

for (let i = 0; i < 16; i++) {
// 这里是块区域
}
var 没有块级作用域,for循环中变量 i 的定义位置提升到 for 循环之前,循环中的 i 占用的内存空间都是相同的;
let 是有块级作用域的,每次循环都会重新定义一个 i ,占用一个新的内存空间;各个 i 之间都不会相互干扰;

@Limeijuan
Copy link

因为let声明的范围是块作用域;每个花括号就是一个块作用域,每个块作用域的变量相隔离;而var 声明是函数作用域,var可以重复定义一个变量,覆盖之前的值;

@wzl624
Copy link

wzl624 commented Jan 15, 2022

  • 在js中作用域只有函数作用域和全局作用域。

  • 在函数体内使用var 定义的变量,会被提到函数开始处进行定义,作用域为整个函数,在if或者for循环中声明的变量会泄露成全局变量。

  • 而let、const定义的变量具有块级作用域,而外层作用域无法读取内层作用域的变量;
    {{{
    {let tmp = "hello world";}
    console.log(tmp); //error
    }}}

  • 内层作用域可以定义外层作用域的同名变量
    {{{
    let tmp = "hello world";
    {let tmp = "hello world";}
    }}}

@zcma11
Copy link

zcma11 commented Jan 15, 2022

let声明的变量会形成块级作用域,每一次循环都是在一个块里面声明新的变量,于是就相互独立了。而 var 声明的变量会被提升到全局,所以每次循环都是用同一个变量,然后重新赋值。

@bianzheCN
Copy link

回答

let 通过块级作用域去解决,循环陷阱造成的原因是用 var 声明的变量是一个全局变量,循环后拿到的永远是最新值

展开

let 声明的变量仅在块级作用域生效,

所以 for 循环中,每个循环的 i 是一个新的变量,仅在本轮循环有效。

另外这个 let i 对于循环的代码块是一个父级作用域,engine 每次循环会拿到上次循环的 i 值,从新计算本轮循环 i 值,所以循环的代码块里拿到的是本轮从新计算的新的 i

@chunhuigao
Copy link

chunhuigao commented Jan 15, 2022

  • var声明的变量会被提升到函数作用域;并且会被覆盖;在执行数组中函数时,函数本级执行上下文没有i这个变量,所以会向上级执行上下文中寻找;上级执行上下文中i已经被for循环修改为10的,所以执行函数中的function不会输出10;
  • let声明的变量在块级作用域生效,每个块级作用域的变量相互隔离,且声明的变量不会被提升;所以每次执行数组中的函数时,向上级执行上下文寻找i这个变量时寻找到的是定义时的值;

@alec1815
Copy link

es5不存在块级作用域,所以迭代变量i提升,然后对于a数组内每一个函数内的i都是向上查询作用域a的,所以结果是10
let声明的变量i由于是块级作用域,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6

@zzzz-bang
Copy link

zzzz-bang commented Jan 15, 2022

  1. 那是因为for不是块级作用域,变量的访问是从当前作用域开始,顺着作用域链向上查找的,变量是全局变量
  2. let 的作用域是块级的,也就是说,在for循环中,每一次的都会重新定义一个变量 。

@aiuluna
Copy link

aiuluna commented Jan 15, 2022

var声明的变量不会有块级作用域,该变量向上提升到全局作用域,所以执行的都是i最后的值
let声明的变量具有块级作用域,所以每次循环会重新定义新的变量i

@yanzefeng
Copy link

var 声明的变量会提升到全局作用域,所以执行的都是i最后的值
块级作用域是以{}标识的,for循环应该生成n个{}子块作用域每个子块{}中关联的是相应的i,即每次循环的i值

@yaoqq632319345
Copy link

由于var声明不存在块级作用域,所以for循环中每次循环都会覆盖变量的值,而let则存在块级作用域,每次for循环中对于遍历的创建的函数都是独立的作用域,在没有let之前,解决这个问题需要创建函数作用域来解决

@JanusJiang1
Copy link

使用var会有问题是因为js没有块级作用域,只有函数作用域

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
console.log(i)
//以上for循环里的 i 我们理想是只在for循环里使用生效,**但实际上 i 是声明在外部作用域之中的。**
// 这就意味着此处的i  ,console.log(i);已经是10 了,且作用域内之后任何时候访问都是10,所以执行以下表达式,结果是10
a[6](); // 10

let能解决这个问题是因为,es6中使用let,和const实现了块级作用域。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
console.log(i)
// 此时的 i 是声明在for循环内的,且{ }中是独立的块级作用域, 它将 i 重新声明在了for循环的**每一次**迭代中。
// 此处console.log(i) => 未定义。
//所以此处执行a[6]函数,打印的i是当时声明在当时层的块级作用域中的 let i =6。
a[6](); // 6

@BambooSword
Copy link

BambooSword commented Jan 15, 2022

通过var声明的变量没有块级作用域。
在语句块里声明的变量的作用域所设置变量的影响会在超出语句块本身之外持续存在。
换句话说,语句块{}不会引入一个作用域。

var x = 1;
 {
   var x = 2;
 }
 console.log(x); // 输出 2

使用let和 const 相比之下,使用 let和const声明的变量是有块级作用域的。

let x = 1;
{
  let x = 2;
}
console.log(x); // 输出 1

使用let声明的变量在块级作用域内能强制执行更新变量,下面的两个例子对比:

var a = [];
for (var i = 0; i < 10; i++) {
      a[i] = function () {console.log(i);};
}
a[0]();                // 10
a[1]();                // 10
a[6]();                // 10

/********************/

var a = [];
for (let i = 0; i < 10; i++) {
      a[i] = function () {console.log(i);};
}
a[0]();                // 0
a[1]();                // 1
a[6]();                // 6
```js

@792472461
Copy link

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

也就是说let定义的变量只有块级作用域内有效

@rachern
Copy link

rachern commented Jan 15, 2022

循环陷阱产生的原因是因为使用 var 声明的变量,是在当前的函数作用域或者是全局作用域生效的,所有的循环使用的都是同一个变量的值,所以会导致打印出来的值都是该变量最后的值

使用 let 能够解决循环陷阱是因为 let 产生了一个块级作用域,每个块级作用域都重新定义了一个变量,该变量只在当前块级作用域生效,每个块级作用域使用的都是不同的变量,因此不会产生循环陷阱

@qytayh
Copy link

qytayh commented Jan 15, 2022

for循环头部的let i不仅为循环本身声明了一个i,而是为循环的每一次迭代都更新声明了一个新的i。这意味着loop迭代内部创建的闭包封闭是每次迭代中的变量,也正如我们的期望一样。

var在外层作用域中只有一个i,这个i被封闭进去,而不是每次迭代都会有一个新的i

@jj56313751
Copy link

  • for循环中var声明的变量i作用域在for循环的外层,for循环内部声明的函数中引用的i指向的都是同一个i,导致输出的都是同一个数
  • let生成一个块级作用域,每次循环的i都在当前块级作用域内有效,因此最后输出的都是不同的值

@zhenyuWang
Copy link

问题

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

可以看到当我们在 for 循环中用 var 声明变量的时候,内部函数得到的值是循环结束后的值,而不是本次循环的值。
这是因为用 var 声明的变量没有块级作用域,所以此时 i 属于 for 循环所在作用域中的变量,所以当我们执行函数的时候,每个函数都指向了同一个变量,而此时的变量 i 因为已经结束了循环,变成了 10,所以执行函数得到的结果是 10

let 做了什么

for 循环头部使用 let 声明变量 i 不止为 for 循环本身声明了一个 i,而是为循环的每一次迭代都重新声明了一个新的 i。所以循环内部的函数闭包封闭的是每次迭代中的变量,就像我们期望的那样。

@jiafei-cat
Copy link

var 声明的i会提升到当前函数的作用域,所以最后输出10
let 声明的i式块级作用域,及花括号内的每次循环都是新的i

@guoshukun1994
Copy link

循环陷阱指的是用var声明循环变量:
for(var i = 0; i < 10; i++) { setTimeout(()=> { console.log(i); }, 0); }

这里会输出什么呢?
10次定时器输出的10;显然不是我们想要的结果,我们想这个定时器从0到9记录输出;

为什么会这样呢?
这里用var定义循环变量i,i会被提升为全局变量,而setTimeout回调函数形成了个闭包环境引用了i,setTimeout回调执行需要等主线程的循环任务结束才会开始执行;
最后一个循环得到了9,执行i++与判断条件相比不符合,结束循环,此时i=10了,意味着全局变量i等于10,所以setTimeout输出了10个10;

为什么用let能解决呢?
for(let i = 0; i < 10; i++) { setTimeout(()=> { console.log(i); }, 0); }

  1. 首先let定义循环变量,每次循环都会产生一个块级作用域
  2. Javascript引擎会记住上一次for循环的值来初始化本次循环变量,
  3. 每次创建的闭包定时器都会引用对应循环的变量i,所以最后输出的结果是从0到9的输出值;

总结一下:
利用let声明的循环变量在每次循环都会创建一个独立的块级作用域,在循环内部创建的闭包环境每次应用的都是对应循环变量的值,不会被变量提升指全局,每次闭包环境中引用的就是对应块级作用域中的变量,所以说let能够解决循环陷阱

@superjunjin
Copy link

let声明的变量只在其声明的块作用域有效,简单的说块级作用域就是一个{},在这里每一次进入for循环都会有一个独立的块级作用域{},所以只会输出当前块级作用域循环到的值,而不会再向上级作用域查找。

@crazyyoung1020
Copy link

crazyyoung1020 commented Jan 15, 2022

var 声明的变量在整个函数作用域内都有效
而let和const声明的变量只在当前块级作用域内有效

这也就导致如下两种不同的情况
也就解决var的循环陷阱

function test(){
    // var 声明的变量在当前函数作用域内都有效
    for(var i=0;i<6;i++){
        console.log(i)
    }
    console.log(i);// 6
}
test()

function test1(){
    // let声明的变量只在循环块内才生效
    for(let i=0;i<6;i++){
        console.log(i)
    }
    console.log(i);// i is not defined
}
test1()

let和const是如何支持块级作用域的呢?

在执行上下文中,扫描变量阶段
整个函数执行上下文中var 申明的变量,都会被放到了变量环境里,
而函数执行上下文中最外层的块内的let 申明的变量,被放到了词法环境栈里,词法环境只会存放当前块内的let申明的变量以及上层块内申明的变量。随着代码的执行,进入内部块后,会将内部块内let申明的变量推入词法环境栈顶,以此类推。

执行代码的代码的时候,当遇到变量,会先在当前词法环境内自顶向下寻找变量,如果没找到,再去变量环境寻找,当跳出当前块后,词法环境栈顶的所有变量弹出。这也就导致了let和const只会在所属词法环境对应的块级作用域内才有效。

这也就是let和const在整个代码编译和执行过程中的支持块级作用域的原理。

@alienRidingCat
Copy link

在ES5标准中,for循环都是通过var来声明变量,而在js中,var是没有独立的作用域的,变量的访问会从当前作用域开始,顺着作用域链向上查找,因此,使用var声明在循环中的作用域实际上是共用的,var所定义的变量i也是共用的,因此当在循环中使用i绑定事件,其在执行时,for循环早就执行完了,变量i也多次被赋值。
使用let可以解决循环陷阱,是因为let将变量绑定到当前所在的作用域中,形成一个块级作用域,每一次进入for循环都会有一个新的块级作用域,在事件响应函数执行时要访问变量i,会从它所在的块级作用域查找,此时的i相当于是被重新定义的。
循环陷阱问题通常可以使用闭包的方式去模拟块级作用域解决,let使得这一问题的解决方案变得更简单。

@partiallove
Copy link

如果使用var声明变量进行for循环会有问题,是因为js没有块级作用域,只有函数作用域。
所以变量i是公用的,导致变量被多次赋值,输出的结果并不是我们所期望的。
let能解决这个问题是因为,es6中使用let和const实现了块级作用域。
每次循环都会创建一个独立的块级作用域,在循环内部创建的闭包环境每次应用的都是对应循环变量的值,不会被变量提升指全局,每次闭包环境中引用的就是对应块级作用域中的变量,所以说let能够解决循环陷阱。

@rhythm022
Copy link

原来会有循环陷阱问题,是因为我们用了var关键字去声明变量,变量就会被放在块所在的函数的作用域里面,这样子的话,你N次循环访问到的其实都是同一个变量。

那么用了let之后,let声明的变量是放在块本地的本地的作用域里面的,那么你N次循环会有N个块,然后,他们会有各自的变量,是互相独立的,这样就不会有循环陷阱的问题。

@su37josephxia su37josephxia changed the title let为什么能够解决循环陷阱 Day15 - let为什么能够解决循环陷阱 Jan 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests