这是我参与「第四届青训营 」笔记创作活动的的第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";
}
总结
三者的区别
| var | let | const |
|---|---|---|
| 函数作用域 | 块级作用域 | 块级作用域 |
| 域内可修改且可重声明 | 域内可修改但不可重声明 | 域内不可修改且不可重声明 |
| 由于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
有两种风格:
- 尽量不使用var,声明变量只在值不会更改时使用const,其余都使用let。
- 尽量不使用var,声明变量只在值会更改时使用let,其余都使用const。
个人常常不知不觉修改了let变量,因此选择风格2。
参考:
- 《JavaScript权威指南》第七版 作者:David Flanagan
- 《深入理解ES6》作者:Nicholas C. Zakas
- Difference between var, let and const keywords in JavaScript
- Hoisting