在JS中,声明变量的关键字有三种,分别是var、const和let。
其中,var在任何版本的ECMAScript中都可以使用,而let和const只能在ES6(ES2015)及之后才能使用。
var关键字
var关键字(var是一个关键字)可以定义一个变量,后跟变量名。
var具有如下几个特点:
- 声明范围时函数级作用域
- 存在作用域提升(变量提升)现象
- 重复声明不报错
- 全局声明的变量自动成为window对象的属性
案例1:使用var定义的变量会成为包含它的函数的局部变量
function test(){
var msg = "hello world";//此时,var定义的变量是局部变量
}
test();
console.log(msg);//报错
在上面代码中,我们在函数体内用var定义了一个变量,但它会变成局部变量,只能用于函数体中。也就是说,在函数执行完成后,里面的局部变量就会被销毁,函数体外无法使用。但是,如果在函数体中删除var操作符,就可以将msg定义成全局变量,这时我们在函数体外就可以调用:
function test(){
msg = "hello world";//此时,var定义的变量是全局变量(不推荐这么做)
}
test();
console.log(msg);//打印结果为"hello world"
案例2:var的作用域提升(变量提升)现象
使用var时,下面的代码不会报错。因为使用这个关键字声明的变量会自动提升到函数作用域顶部:
function test(){
console.log(msg);
var msg = "hello world";
}
test();//undefined
之所以不会报错,是因为ECMAScript运行时将其等价于如下代码来执行:
function test(){
var msg;
console.log(msg);
msg = "hello world";
}
test();//undefined
这种现象就是所谓的“提升”(hoist)现象,也就是把所有变量声明都拉到函数作用域顶部。
let声明
let与var的作用相似,但区别很大。let有如下几个特点:
- 声明范围是块级作用域
- 没有提升现象,但有暂时性死区问题(TDZ)
- 不能重复声明
- 全局声明不是window对象的属性
案例1:let作用域仅限在块内部
所谓的块,简单来理解就是一个花括号内部就是一个块
if(true){
let age = 26;
console.log(age);//26
}
console.log(age); //ReferenceError: age没有定义
在这里,age变量之所以不能在if块中外部被调用,是因为它的作用域仅限于该块内部。
案例2:暂时性死区(TDZ)
我们来对比一下var和let在下面情形中的不同表现:
//name会被提升
console.log(name);//undefined
var name = 'Matt';
//age不会被提升
console.log(age);//ReferenceError: age没有定义
let age = 26;
在let声明之前执行瞬间被称为暂时性死区,自此阶段引用任何后面才声明的变量都会抛出ReferenceError,而用var定义,则会出现提升现象,不会报错。
案例3:for循环中的let声明
有如下一个循环:
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},0)
}
//输出结果:5,5,5,5,5
之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值。
而在使用let迭代变量时,JS引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以console.log输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},0)
}
//输出结果:0,1,2,3,4
const声明
const具有let所有特点:无变量提升、具有块级作用域、存在暂时性锁区、只允许声明一次。在此基础上,它还有如下特点:
- 声明变量时必须初始化
- 初始化后不可以修改
- 不能修改仅限于常量的引用
- 不能用于声明会自增的迭代变量
案列1:const声明的限制只适用于它指向变量的引用
const不允许被修改。但const变量引用若是一个对象,那么修改这个对象内部的属性并不违反const的限制。
const person = {};
person.name = 'Matt'; //修改成功,且不会报错
案例2:const声明迭代变量(会报错)
由于迭代变量会自增,所以会和const中不允许被修改的特点相冲突,结果会报错
for(const i = 0 ; i < 10; ++i){} //TypeError:给变量赋值
开发中最佳使用建议:
- 不使用var
- 优先使用const,let次之
参考书籍:《JavaScript高级程序设计(第四版)》