var,let,const命令

134 阅读5分钟

var 变量提升

在es6之前,变量 都是用 var 命令来声明的。 用 var 命令声明常常会发生 变量提升 的情况。

console.log(a) // 输出undefined
var a = 1

上面的代码明明a还没有被声明但是却可以使用,并没有报错。这就是 变量提升 之后所产生的情况,上面的代码在运行时就相当于下面的代码。

var a
console.log(a) // 输出 undefined
a = 1

这种情况常常会误导我们,为了纠正这种情况,ES6新增了letconst命令。如果变量在letconst声明前使用就会报错。

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。但是这里在循环结束后依旧可以访问到变量ii泄露成为了全局变量。

没有块级作用域经常会给我们带来一些麻烦,比如下面的例子:

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

chrome调试

上图略过了for循环,在 chrome 进行断点调试,直接到setTimeout函数执行的时候,可以看到右边的 Closure 中有变量 i 的值,这表示这是这个闭包作用域链上的变量。

let 和 const

我们用自执行函数的方式来模拟块级作用域还是比较麻烦的。有了 let 之后,一切变得简单了。

for (let i=0; i < 10; i++){ 
  setTimeout(()=>{
    console.log(i)
  },1000)
}
// 输出0到9

chrome调试
从 chrome 断点调试中能看到,用 let 声明变量之后,右边会有一个 block 用来表示块级作用域。

constlet 同样,声明的变量只在块级作用域有效,区别在于:

  1. const 声明变量必须同时赋值,不能仅声明不赋值
const a // Uncaught SyntaxError: Missing initializer in const declaration
a = '1'
  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)执行时依然报错了。这是因为块级作用域内存在 暂时性死区 。块级作用域内 letconst 声明的变量会绑定这个作用域,不再受外部影响,变量在还没有声明完成前使用,就会报错。

不再用 var

var 声明的确带来了很多迷惑性,ES6加入的 letconst块级作用域暂时性死区 可以规范的声明与使用变量,减少错误的发生。 可以完全使用letconst 来替代 var


原文地址

参考