var 变量提升
在es6之前,变量 都是用 var 命令来声明的。
用 var 命令声明常常会发生 变量提升 的情况。
console.log(a) // 输出undefined
var a = 1
上面的代码明明a还没有被声明但是却可以使用,并没有报错。这就是 变量提升 之后所产生的情况,上面的代码在运行时就相当于下面的代码。
var a
console.log(a) // 输出 undefined
a = 1
这种情况常常会误导我们,为了纠正这种情况,ES6新增了let 与 const命令。如果变量在let 和 const声明前使用就会报错。
console.log(a) // 报错 Uncaught ReferenceError: Cannot access 'a' before...
let a = 1
块级作用域
类C语言中,由 {} 封闭起来的代码都有自己的作用域,但是在ES6之前,Javascript是没有块级作用域的,比如在for循环中声明变量,来看下面的例子:
for (var i=0; i < 10; i++){
// 其他代码
}
console.log(i)
一般我们只希望在for循环内部用到变量i,循环结束之后不希望访问到变量i。但是这里在循环结束后依旧可以访问到变量i,i泄露成为了全局变量。
没有块级作用域经常会给我们带来一些麻烦,比如下面的例子:
for (var i=0; i < 10; i++){
setTimeout(()=>{
console.log(i)
},1000)
}
我们原本希望打印结果是从 0到9,但事实上上面的代码打印了 10个10。
解释原因之前先了解下作用域链的概念,这里引用一下《JavaScript高级程序设计第三版》一书中对于作用域链的描述:
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延 续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
上面setTimeout中的代码由于事件循环的机制,会在 for循环 结束之后依次执行console.log(i)这句代码,寻找当前作用域中的 变量i,没有找到于是向外找到 全局变量i,全局变量i 此时就是10。
在没有let之前,我们如果想让打印的值为 0到9的话,可以这样改:
for (var i=0; i < 10; i++){
(function(index){
setTimeout(()=>{
console.log(index)
},1000)
})(i)
}
上面的代码将 setTimeout 放到了一个 自执行函数中,这样就产生了一个 闭包, 闭包 是指有权访问另一个函数作用域中的变量的函数,这里指的是 setTimeout 中的函数,它的作用域链中包含了 自执行函数的作用域。
现在 setTimeout 中的函数在执行console.log(index)这句代码时,寻找当前作用域中的 变量i,没有找到于是向外找到 自执行函数活动对象中的arguments的index,然后正确打印 0到9。

上图略过了for循环,在 chrome 进行断点调试,直接到setTimeout函数执行的时候,可以看到右边的 Closure 中有变量 i 的值,这表示这是这个闭包作用域链上的变量。
let 和 const
我们用自执行函数的方式来模拟块级作用域还是比较麻烦的。有了 let 之后,一切变得简单了。
for (let i=0; i < 10; i++){
setTimeout(()=>{
console.log(i)
},1000)
}
// 输出0到9

let 声明变量之后,右边会有一个 block 用来表示块级作用域。
const 和 let 同样,声明的变量只在块级作用域有效,区别在于:
const声明变量必须同时赋值,不能仅声明不赋值
const a // Uncaught SyntaxError: Missing initializer in const declaration
a = '1'
const声明的变量的值是不能被更改。
const a = '1'
a = '2' // Assignment to constant variable.
需要注意的是:如果声明的是一个引用类型的值的话,因为变量的值实际上是个指针,这个指针指向存储在堆中的一个对象,所以 const 只能保证这个变量也就是指针不能被更改,指针所指向的对象依旧是可以更改的。
//修改对象不会报错
const people = {
name:'xiaoming'
}
people.age = '18'
console.log(people) //{name: "xiaoming", age: "18"}
//将变量的值更改为指向另一个新对象的指针会报错
const people2 = {
name:'xiaohong'
}
people2 = {
name:'xiaogang'
}
console.log(people2) //Uncaught TypeError: Assignment to constant variable.
暂时性死区
var a = 1
function f(){
console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
let a
}
f()
这里虽然在最外层已经声明过变量 a,但是console.log(a)执行时依然报错了。这是因为块级作用域内存在 暂时性死区 。块级作用域内 let 和 const 声明的变量会绑定这个作用域,不再受外部影响,变量在还没有声明完成前使用,就会报错。
不再用 var
用 var 声明的确带来了很多迷惑性,ES6加入的 let 和 const,块级作用域 与 暂时性死区 可以规范的声明与使用变量,减少错误的发生。
可以完全使用let 和 const 来替代 var。