var, let, const总结 | 青训营笔记

86 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的的第1天

var

在ES6之前,使用var关键字是JS的变量声明的唯一方式。

var text = "我重伤倒地了!";

随着JS不断发展,人们发现使用var声明的变量,其使用的hoisting机制(注意提升的是声明而不是初始化,这意味着就算提升了有初始化的变量声明,初始化的工作依然留在原地,只有声明提升了!)使得开发者易在访问变量时出现混淆而导致出错。

function sayName(condition) {
    /*
        当条件判断本人为真时,返回本人名字,否则预期函数会报错,因为name变量并未声明,赋的值无意义。
        而实际上,函数会返回"not me"
    */
    if (condition) {
        var name = "Jack Young";
        return name;
    } else {
        name = "not me";
        return name;
    }
}

上面的示例易让人以为name只有在条件为真时才创建变量name,实际上,无论条件真假,name都会在JS引擎预编译时创建。JS引擎会将上述sayName函数转换为:

function sayName(condition) {
    # 为真时的var声明已被提升到函数顶层
    var name;
    if (condition) {
    name = "Jack Young";
    return name;
    } else {
        name = "not me";
        return name
    }
}

var声明的name变量被提升到函数顶部,由于变量未赋值前其值为undefined,所以函数并不会如预期那有报错,而是返回undefined

这样容易让开发者产生混淆,非常不便于debug,因此ES6引入了块级作用域来控制变量的生命周期。

let

let的语法与var一致,但let不存在提升机制,因此用let代替var就可以把变量的作用域控制在当前代码块中。我们可以放心地在块级顶部使用let声明变量而不担心变量被其他块访问,从而避免出现预期外的行为。

function sayName(condition) {
    # 此时除if块内可访问name外,其余块都不能访问它。因此此段代码会报错。执行完毕if语句后,name变量即被销毁
    if (condition) {
        let name = "Jack Young";
        return name;
    } else {
        return name;
    }
}

const

const声明的是常量,必须进行初始化,赋值不可修改。

const PI = 3.14;
const G; // x 常量初始化
PI = 3.1415926; // x 常量不可修改

const也是块级作用域标识符,故除当前块外,一旦执行完当前块变量就会被销毁。

if (cond) {
    const PI = 3.14;
}

// 错误,不存在PI变量
console.log(PI);

const声明的常量若是非对象类型,则不能修改,若类型是对象,则可修改对象中的值。

const cat = {
    name: "Niu Niu";
}

// 可修改对象属性值
cat.name = "Biu Biu";

// 不可修改对象绑定
cat = {
    name: "Biu Biu";
}

临时死区(Temporal Dead Zone)

若在块内使用let和const声明的变量之前访问变量,会触发引用错误,这是由于二者并不提升作用域。

if (cond) {
    console.log(typeof name); // Uncaught ReferenceError: Cannot access 'name' before initialization
    let name = "Jack Young";
}

JS的引擎在扫描代码发现变量声明时,要么把var声明的变量提升作用域,要么把let,const声明的变量放到TDZ中。访问TDZ中的变量会触发运行时错误。只有执行完成变量声明后,变量才会从TDZ中移除,然后才能正常访问。

若在块之前访问变量,不会报错,而是返回undefined,这是因为块之外变量已经被销毁,失去绑定。

console.log(typeof name); // undefined

if (cond) {
    let name = "Jack Young";
}

总结

三者的区别

varletconst
函数作用域块级作用域块级作用域
域内可修改且可重声明域内可修改但不可重声明域内不可修改且不可重声明
由于hoisting机制,可以先访问后声明声明前不可访问声明前不可访问,且声明时必须初始化
可以只声明不初始化可以只声明不初始化不可以只声明不初始化
全局域中声明会创建一个新的全局变量作为全局对象,若已存在同名的全局属性,会被var覆盖,即不能通过属性名访问它不会创建全局对象,若已存在同名的全局属性,只会暂时遮蔽它,依然可通过如window.RegExp访问它同let

全局域中var生成的全局对象覆盖原属性

var RegExp = "Jack";
console.log(window.RegExp); // "Jack"

window.RegExp === RegExp // true

全局域中let,const生成的全局对象遮蔽原属性

let RegExp = "Young";
console.log(RegExp); // "Young"

RegExp === window.RegExp // false

关于何时使用let与const

有两种风格:

  1. 尽量不使用var,声明变量只在值不会更改时使用const,其余都使用let。
  2. 尽量不使用var,声明变量只在值会更改时使用let,其余都使用const。

个人常常不知不觉修改了let变量,因此选择风格2。

参考: