相信大家在日常开发中经常用到for循环,比如for (var i = 0; i < 1; i++)
for (let i = 0; i < 1; i++)
上述两个for循环语句,差别就是声明i的时候一个使用var声明,一个使用let声明,相信大家都知道let和var的区别,这里就不再赘述,可你们知道这里的深层原理是什么吗,是什么导致了他们之间的区别呢,他们的区别又会造成什么样的影响呢?
语句块级作用域
在JavaScript中,只有4种语句拥有块级作用域:
// try catch 中每一个大括号都是一个作用域
try {
} catch(e) {
} finally {
}
with(x) /* 这里有个作用域 */
{
// 这里是个作用域
}
上面举例的三种,try-catch-finally语句中看似也是利用了{}
划分的作用域,但是因为try-catch-finally每一部分都不能允许出现单语句,所以它是独立的一类,with语句后面不加{}
是一个作用域(即使它是单语句),也可以利用{}
包裹起来形成块。
每一个语句块都有返回值,可以用eval查看他们的返回值,例如
console.log(eval(`
try {
1 + 1
} finally {
2 + 2
}
`));
其他像if语句是没有块作用域的,if后面可以接单语句也可以接{}
,{}
会形成一个作用域。
如果像if语句是有块作用域的话,那么以下代码是可以成立的
if (true) let a = 1;
如果if会形成一个作用域,那么我在作用域内是可以声明一个变量的,实际上这么做的话会报Lexical declaration cannot appear in a single-statement context
。
刚才讲了3种,还有一种呢?
还有一种就是for(let/const)循环语句。
for循环包含了for语句和循环体,循环体可以是单语句也可以是{}
块。
因为历史原因,之前js只有global scope 和 function scope,var声明的变量会登记在最近的上述作用域中。而let/const声明是放在词法作用域,所以声明方式不同导致了for(var)没必要形成一个单独的作用域。
for循环中的隐蔽之处
我先说一下for(let/const)循环作用域的结构
for (let i = 0; i < 10; i++) // 作用域A
{
// 作用域B
console.log(i);
}
/*
作用域如下:
A: {
B: {
// 10个副本
}
}
*/
what?有这么多作用域,而且作用域B还有10个副本?
先看看为什么会有两个作用域的分割(A和B)。
先看看下面的代码
for (let i in obj) {
console.log(i);
}
假设for循环只需要一个作用域,那么在每一次遍历的时候都重新进行i的声明,造成遍历重复声明的后果,所以这里实际上是这样的:外层有一个forEnv,作声明i,里面有一个loopEnv,作遍历,这样声明只有一次,并且遍历过程也能访问到i。
那刚才的例子为什么B作用域有10个副本呢?
相信大家都知道for循环中,每一次循环的变量在当前次循环能访问到正确的值
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), i * 10);
}
看到这个例子相信大家都知道我在讲什么了,现象是每次循环中都有一个定时任务,而这个定时任务触发的时候能正确地访问到当前次循环的i值。如果i只有一个,那么循环结束后i的值应该是10,所有的定时回调应该都是打印10才对。所以for(let/const)循环中,每一次循环都是作用域的副本,所以for(let/const)循环可能并不比执行10次函数的开销小。
参考
《JavaScript核心原理解析》周爱民
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。