理解 let 的个性是从 MDN 的文档,我失去的信息有这么几条:

  • let 申明的变量的作用域是块级的;
  • let 不能反复申明已存在的变量;
  • let 有临时死区,不会被晋升。

大部分人应该都是这么认为的,这个了解「没有问题」,然而不够「全面和粗浅」。

质疑:
for (var i = 0; i<=5; i++){  setTimeout(()=>{console.log(i)})}

大家都晓得会打印出 6 个 6。如果把 var 改成 let ,就会别离打印出 0、1、2、3、4、5:

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

然而,用后面的常识并不能很好的从原理上来解释这个起因。

于是我去看 MDN 的例子

发现鸡贼的 MDN 奇妙地避开了这个问题,它的例子是这样的:

为什么 MDN 要成心申明一个 j 呢,为什么不间接用 i 呢?我猜想 MDN 为了简化常识,瞒哄了什么。
于是我去看了看 ES6原英文文档。

总结:
一、当for循环中应用let关键字的时候,在这个()块中会生成一个作用域,咱们称之为词法作用域或者块级作用域。ES5之前JavaScript只有global scope 和 function scope,var申明的变量会注销在最近的上述作用域中。例如:for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个暗藏的作用域
for (let i = 0; i < 10; i++) // 作用域A{    // 作用域B    console.log(i);}/*    作用域如下:    A: {        B: {            i 变量在B作用域中            // 10个正本        }    } */
二、在for循环中变量i是由var申明的所以变量i会被晋升至for循环外的作用域顶部,所以即使是在循环之外也能够拜访变量i。例如:
for (var i = 0; i < 10; i++) {  process(items[i]);}console.log(i)  //在这里任然能够拜访变量iconsole.log(i)  //但如果换成let在这里不能够拜访变量i,抛出谬误
三、for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中从新申明及初始化一次。联合一代码块来看,10个正本B作用域中都会从新申明及初始化一次i,那么如果B作用域中如果是一个函数setTimeout(()=>{console.log(i)}),产生了闭包。那么闭包中的i自然而然仍放弃着对外部B作用域中流动对象i的援用。后果如下:

四、顺着来解释为什么for (var i = 0; i<=5; i++){setTimeout(()=>{console.log(i)})}输入是6个6。如下图在每次执行循环体之前,i并不会再循环体上下文中从新申明和初始化一次,这个步骤在i理论所在内部就近函数或者全局函数中实现。因而,当A作用域中如果是一个函数setTimeout(()=>{console.log(i)}),产生了闭包。按照闭包中i仍放弃着对外部A作用域中流动对象i的援用的逻辑,然而作用域A中并没用i。所以顺着作用域链往上找,直到找到理论i所在内部就近函数或者全局函数,找到了还须要等循环走完,返回理论i为6的值。
for (var i = 0; i < 10; i++){    // 作用域A    console.log(i);}/*    作用域如下:    i 在内部就近函数或者全局函数中    A: {        // 10个正本     } */
五、咱们用一个代码块来补充四中的解释。下图咱们能够看到如果咱们把i赋值给j并放入A作用域中的话,如果外部还是闭包,那么根据闭包准则,仍旧能实现对A作用域中变量j的援用,所以能失去咱们想要的后果。
for (var i = 0; i < 10; i++){    let j = i    // 作用域A    console.log(i);}/*    作用域如下:    A: {        j 在A作用域中        // 10个正本     } */