当前位置: 首页 > 后端技术 > Node.js

For循环中的let和var

时间:2023-04-03 16:57:24 Node.js

从MDN文档上了解let的特性。得到如下信息:let声明的变量作用域是块级的;let不能重复声明已有变量;let有一个临时死区,不会被挂起。大多数人应该是这样认为的。这种理解“没有问题”,但并不“全面深刻”。问题:for(vari=0;i<=5;i++){setTimeout(()=>{console.log(i)})}大家都知道会打印出6个6。如果把var改成let,会分别打印出0、1、2、3、4、5:for(leti=0;i<=5;i++){setTimeout(()=>{console.log(i)})}但是,之前的知识在原理上并不能很好的解释这个原因。于是去看了MDN的例子,发现鸡贼的MDN巧妙的避开了这个问题。它的例子是这样的:为什么MDN故意声明一个j,为什么不直接使用i呢?我猜想MDN为了简化知识而隐藏了一些东西。于是就去看了ES6英文原版文档。总结:1、for循环中使用let关键字时,会在this()块中产生一个作用域,我们称之为词法作用域或块级作用域。在ES5之前,JavaScript只有全局作用域和函数作用域,var声明的变量会被注册到最近的上述作用域中。例如:for(leti=0;i<5;i++)这句话的括号之间有一个隐藏作用域for(leti=0;i<10;i++)//ScopeA{//ScopeBconsole.log(i);}/*作用域如下:A:{B:{i变量在B作用域中//10份}}*/其次,在for循环中,创建变量i变量i将被提升到for循环外的作用域顶部,因此即使在循环外也可以访问变量i。例如:for(vari=0;i<10;i++){process(items[i]);}console.log(i)//这里仍然可以访问变量iconsole.log(i)//但是如果改成let,这里的变量i是访问不到的,会报错。3.for(leti=0;i<5;i++){loopbody}在每次执行循环体之前,JS引擎都会将i在循环体中重新声明并在上下文中初始化一次。根据一个代码块,i会在B的10份范围内被重新声明并初始化一次,那么如果B的范围内有一个函数setTimeout(()=>{console.log(i)}),它将生成闭包。那么闭包中的i自然仍然持有对外部B作用域中的活动对象i的引用。结果如下:4、接着解释为什么for(vari=0;i<=5;i++){setTimeout(()=>{console.log(i)})}的输出是6个6s.如下图所示,在每次循环体执行之前,i不会在循环体的上下文中重新声明和初始化一次。这一步是在i实际所在的外部最近函数或全局函数中完成的。因此,当A作用域为函数setTimeout(()=>{console.log(i)})时,会产生一个闭包。根据逻辑闭包中的i仍然保持着对外层A作用域中活动对象i的引用,但是i在作用域A中并没有被使用。所以沿着作用域链往上查找,直到找到最近的外部函数或者全局函数实际i所在的位置。如果找到,则需要等待循环结束,将实际i的值返回6。for(vari=0;i<10;i++){//scopeAconsole.log(i);}/*作用域如下:i在外部最近的函数或全局函数中A:{//10份}*/五,我们用一个代码块来补充四中的解释。在下图中我们可以看到,如果我们将i赋值给j,放在A作用域中,如果内部还是一个闭包,那么根据闭包原则,A作用域中对变量j的引用仍然可以完成,这样我们就可以得到我们想要的结果。for(vari=0;i<10;i++){letj=i//scopeAconsole.log(j);}/*scope如下:A:{jisinAscope//10copy}*/