这一条很重要,所以我把它写在最前面:

使用变量声明和块级绑定的最佳的做法是:默认使用const,只有在确实需要改变变量值的时候使用let。这样可以在某种程度上实现代码的不可变,从而防止错误的发生。

 

在ES6之前的ES中,是没有块级作用域的,由此也引出了不少不方便的地方,比如实现循环遍历加监听时,就不能使用理所当然的简单的for循环了,在ES6没出来之前,解决办法只能是采用闭包或者定义临时变量保存this的形式来曲线救国,而ES6的出现,及其带来的块级作用域和新增的两个声明变量的用法:let和const,则彻底的解决了这个令人困扰的问题。

块级作用域最大的作用是强化对变量声明周期的控制,在它尚未出现的ES中,如果使用var 来定义一个变量,则会存在变量提升的问题,这不是一个好现象,因为它会污染全局环境,而且还很容易引起一些错误。而且如果在for、for-in、for-each中使用var,也很容易引起问题,比如刚才提到的在for循环中使用var,看下面的这个Demo:

var funcs=[]
for (var i=0;i<10;i++){
    funcs.push(function () {
        console.log(i)
    })
}
funcs.forEach(function(func){
   func()
})

它的输出结果是10个10,这是因为当循环结束的时候,变量i的值是10,循环里的每一次迭代都同时共享着变量i,其内部创建的函数也全部都保留着对这个相同变量的引用,因此其输出结果会为10个10。

但引入块级作用域后,对于var声明变量带来的变量提升或者是for循环里使用引发错误的问题就会迎刃而解:

Demo:

var funcs=[]
for (let i=0;i<10;i++){
    funcs.push(function () {
        console.log(i)
    })
}
funcs.forEach(function(func){
   func()
})

以上的这个Demo的输出结果是:0~9。它能输出正确结果的原因在于:每次循环的时候let声明都会创建一个新变量i,并且会将它初始化为i的值,所以在循环内部创建的每个函数都能得到属于它们自己的i的值。

下面,我来开始讲解一下块级作用域、let和const。、

块级作用域:

可以用《深入立即ES6》这本书中对块级作用域的描述来总结块级作用域:

  • 块级作用域用于声明在指定块的作用域之外无法访问的变量。块级作用域(其实也是词法作用域)存在于:
    • 函数内部
    • 块中(字符{}之间的区域)

那么ES6中实现块级作用域的形式是什么那?答案是利用let和const关键字。

let:

let有三个特点:

  1. 限制变量在块级作用域中:
    Demo:

    let a=21
    if(true){
        let a=10
        console.log(a)  //10
    }
    console.log(a)  //21
    

    上面的这个Demo中,if后{}中的a就是被let声明限制在了当前的块级作用域中,因此它和在全局作用域中定义的a互不影响。

  2. 不能重复声明:
    Demo:

    let a=9
    let a=10
    console.log(a)
    

    这个Demo会报错:a is already been declared。 当然同一作用域下不能重复声明,如果不是在同一作用域下则可以,比如上面的那个Demo.

  3. 不能变量提升:
    Demo:

    console.log(a)   //a is not defined
    let a=10
    

    这个Demo会报一个 a is not defined的错。

 

const:

const声明有四个特点:

  1. 限制变量在块级作用域中:
    let a=21
    if(true){
        const a=10
        console.log(a)  //10
    }
    console.log(a)  //21
    

    情况和let的第一个特点差不多

  2. const用于声明常量,但必须进行初始化
    const a
    a=10
    console.log(a)
    

    这个Demo会报错,就是因为在用const声明a的时候,没有进行初始化。如果写出这样:

    const a=10
    console.log(a)
    

    则会正常输出a=10

  3. 不能重复声明,也不能重复赋值:
    Demo:不能重复声明:

    const a=9
    const a=10
    console.log(a)
    

    这个Demo会报错。
    Demo:不能重复赋值:

    const a=9
    a=10
    console.log(a)
    

    这个Demo会报一个Assignment to constant variable.的错误。

  4. 不能变量提升:这个特点和let的不能变量提升的特点相同
  5. 用const声明的如果是对象的话,可以修改对象的属性值,但是不能修改对象的绑定:
    Demo:修改对象属性值

    const obj1={
        name:'glc'
    }
    obj1.name="Dyan"
    console.log(obj1.name) //Dyan
    

    对象的属性值修改成功。
    Demo:不能重新绑定对象:

    const obj1={
        name:'glc'
    }
    obj1={
        name:'kafka'
    }
    console.log(obj1.name)
    

    这个Demo会报错:Assignment to constant variable.

 

临时死区:

对于临时死区的描述可见《深入理解ES6》,这里摘录其中的一段:

JavaScript引擎在扫描代码发现变量的声明时,要么将它们提升至作用域顶部(var声明的情况下),要么将声明放到TDZ中(let和const声明的情况下),访问TDZ(Temporal Dead Zone  临时死区)中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移出,然后正常访问。

 

let、const的运用:

一:for循环中的函数:

  • let:第一个Demo中可见。
  • const:const不能运用到for循环中,因为在for循环中,后续会修改变量,这样会违背const不能重复赋值的特点,因此会出错:
    Demo:

    let funcs=[]
    for(const i=0;i<10;i++){
        funcs.push(function () {
            console.log(i)
        })
    }
    funcs.forEach(function (func) {
        func()
    })
    

    这个Demo会报错:Assignment to constant variable。就是因为在后续的循环中修改了变量,所以导致了错误。

二:for-in、for-of循环中:

  • let:
    let funcs=[],
        obj={
            name:'Dyan',
            age:23,
            sex:'女'
        }
    for(let key in obj){
        funcs.push(function () {
            console.log(key)
        })
    }
    funcs.forEach(function (func) {
        func()
    })
    

    输出结果是:name、age、sex。

  • const:
    let funcs=[],
        obj={
            name:'Dyan',
            age:23,
            sex:'女'
        }
    for(const key in obj){
        funcs.push(function () {
            console.log(key)
        })
    }
    funcs.forEach(function (func) {
        func()
    })
    

    输出结果是:name、age、sex。

 

let、const和var的另一个区别:

  • var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器中的window对象)的属性。这样就意味着用var会很可能无意中覆盖一个已经存在的全局属性。这也是我所说的用var声明变量会污染全局环境的原因。
  • 而如果在全局中使用let或const,则会在全局作用域下创建一个新的绑定,但是这个绑定不会添加为全局对象的属性,也就是说,用let和var不能覆盖全局变量,而只是遮蔽掉它,如Demo:
    let Rg='hello'
    console.log(Rg)
    console.log(window.Rg===Rg)  //false
    
    const Nc='world'
    console.log(Nc)
    console.log(window.Nc===Nc)  //false
    

    通过输出结果我们能印证上面所做的描述。