共计 1545 个字符,预计需要花费 4 分钟才能阅读完成。
理解 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) // 在这里任然能够拜访变量 i
console.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 个正本
}
*/
正文完
发表至: javascript
2021-06-11