JavaScript中最常接触的一种变量声明的方式就是var, 我们都知道在JS中存在着预解析, 那么何为预解析呢, 就是当你在一个作用域中不管在什么位置声明一个变量的时候, 会把他放在该做用于最开始的位置, 也就是我们所说的变量声明提升. 当然, 函数也有变量声明提升并且优先级要高于变量, 但是我们主要介绍变量. 预解析是一把双刃剑, 有时会方便我们编程人员, 但是又会为我们带来一些麻烦, 而下面这个例子就是很好地说明变量声明提升的一个弊端:
for(var i=0;i<5;i++) {
setTimeout(function() {
console.log(i)
},0)
}
这是一个for循环, 里面是个定时器, 我们的本意是循环5次, 打印出来的结果应该是1,2,3,4,5,但是在node中打印后我们会发现打印了5个5.name为什么会产生这样的结果呢, 其实原因出在两方面, 第一变量的作用域, 第二,定时器是异步函数
首先, 在ES6以前的JS中, 只有全局作用域和函数作用域, 而没有块级作用域, 所谓块级作用域, 说通俗一点就是一对大括号就是一个块级作用域, 每次for循环一次,都会产生一个块级作用域,但是因为i是var声明的,所以var没有块级作用域的概念, 所有块级作用域都会共享这个变量,i是全局变量,在所有的块级作用域中,都能访问的到,又因为js的预解析,所以,每次在块级作用域中,var 的i 都是变量声明提升到全局的最顶端,且,定时器是异步函数以及JS的单线程,所以他会被放在队列中等待执行,当上述所有代码执行完成后,从上往下执行每个定时器,当除定时器以外的代码执行完毕后,i的值已经变成了5,然后执行5次定时器,所以打印5次5
for循环可以分解成一下这样的形式:
{
var i=0;
if(i<5) {
setTimeout(function() {
console.log(i,1);
})
}
}
{
var i=i+1;
if(i<5) {
setTimeout(function() {
console.log(i,2);
})
}
}
{
var i=i+1;
if(i<5) {
setTimeout(function() {
console.log(i,3);
})
}
}
{
var i=i+1;
if(i<5) {
setTimeout(function() {
console.log(i,4);
})
}
}
{
var i=i+1;
if(i<5) {
setTimeout(function() {
console.log(i,5);
})
}
}
{
var i=i+1;
//条件不满足,不执行if语句
if(i<5) {
setTimeout(function() {
console.log(i,6);
})
}
}
而此时, ES6中新增的一个变量声明的关键字就可以完美的替代var声明变量的方式, 那就是let
let支持块级作用域, 也就是说, 只有在同一块级作用域中, let声明的变量才可以被访问到, 并且, let没有变量声明提升的问题, 这正是在我们开发过程中所倡导的避免全局变量污染的概念, 那么上述相同的代码, 当我们在for循环中用let声明i会发生什么呢:
for(let i=0;i<5;i++) {
setTimeout(function() {
console.log(i)
},0)
}
此时, for还是那个for, 定时器还是那个最后才会执行的异步函数, 但是, 每个for循环产生的块级作用域中已经正确的记录下来了每次循环产生的i的值, 因为let声明的变量在不同作用域不能相互访问, 因此, 每个定时器也都正确的拿到了对应的i值