let
实现:通过引入{}为let提供一个可以绑定的块。
特性:使用 let
做出的声明不存在变量提升。如此,直到声明语句为止,声明将不会“存在”于块儿中。
使用场景:
for (let i=0; i<10; i++) {
console.log( i );
}
console.log( i ); // ReferenceError
实际上,它会对每一次循环的 迭代 重新绑定 i
,确保它被赋予来自上一次循环迭代末尾的值。
这是描绘这种为每次迭代进行绑定的行为的另一种方式:
{
let j;
for (j=0; j<10; j++) {
let i = j; // 每次迭代都重新绑定
console.log( i );
}
}
编译器部分总结
引擎 实际上将会在它解释执行你的 JavaScript 代码之前编译它。编译过程的一部分就是找到所有的声明,并将它们关联在合适的作用域上,这是词法作用域的核心。在你的代码的任何部分被执行之前,所有的声明,变量和函数,都会首先被处理。
变量提升优先级
函数声明和变量声明都会被提升。函数会首先被提升,然后才是变量。
循环和闭包
经典案例:
for (var i=0; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
解析:结果输出为6个6
,并非语义所展现的那样输出0 1 2 3 4 5
,若将定时器设置为setTimeout(...,0)依旧严格遵循循环结束后在执行回调,循环后i为6,仍然输出6个6,由于作用域的工作方式,它们都闭包在同一个共享的全局作用域上,事实上每次循环的为同一个i。
综上,我们需要满足以下两点才能达到预想中的输出:
①为循环的每次迭代都创建一个新的被闭包的作用域
②创建新的i的拷贝
方案一:已知可以通过内部使用IIFE(即立即调用)来创建新的作用域,所以可以通过IIFE 的使用来满足 条件①;只创建空的作用域,并没有从根本解决共享i的问题,所以我们需要在其内部创建一个对外部i的拷贝,即每个作用域创建一个新的i,如下图所示:
方案二:ES6 let实现块作用域的创建,var是只在循环中声明一次,而let是为每次迭代都重新声明一次,每次初始化的值为上次迭代的结果。