用最简单的例子,带你理解var、let、const以及var中的变量提升
var 的变量提升机制
当我们在全局作用域或者块级(局部,函数)作用域中利用var关键字声明变量时,这个变量都会被提升到此时作用域的最顶端,这就是我们在谈到var时最经常说的变量提升。
下面我们看个例子:
const person = (status) => {
if(status){
var name = '前端攻城狮';
}else{
console.log(name);
//undefined
}
console.log(name);
//undefined
}
person(false);
上面的这段代码,很多人可能感觉习以为常了,让我们深究一下。
- 为什么if语句都没有执行,而else语句里和最后输出的name都为undefined而不是报"name is not defined"?
出现undefined的原因我们知道是因为变量定义而未赋值,而这里我们并没有定义变量name。
这里是因为if代码块中的var声明的变量被提升到了函数的顶端。 - 但是疑问又来了,为什么这里if语句没有执行,name变量会被提升到函数顶端呢?
这是因为JavaScript引擎在代码编译时,它会自动将所有代码里面的var关键字声明的语句都会提升到当前作用域的顶端,这也就是为什么if语句没有执行,name变量会被提升到函数顶端。
其实上面的代码可以看作被解析为下面这段代码:
const person = (status) => {
var name;
if(status){
name = '前端攻城狮';
}else{
console.log(name);
//undefined
}
console.log(name);
//undefined
}
person(false);
var在声明变量的时候存在变量提升,所以在ES6中给我们带来了块级声明。块级声明可以理解为只在当前函数下声明的变量有效,或者在代码块和{}括号里有效。
这里就引出了ES6中let声明。
let声明
let声明和var声明一致,都是用来定义变量的,但是使用let不存在变量提升,也就是说let声明的变量只在当前(块级)作用域下有效。我们将上面的例子重写一下。
const person = (status) => {
if(status){
let name = '前端攻城狮';
}else{
console.log(name);
// name is not defined
}
console.log(name);
// name is not defined
}
person(false);
我们将if条件里的var改为let,再次运行后发现,两个输出都报错。这就说明let是块级作用域,所有外面的语句块访问不到,let是没有变量提升的。
let禁止重复声明
如果在同一个作用域中某个变量已经存在,再次使用let关键字声明的话会报错。
var name = '前端攻城狮';
let name = '后端攻城狮';
// 'name' has already been declared
在不同作用域中我们再次声明:
var name = '前端攻城狮';
if(true){
let name = '后端攻城狮';
console.log(name)
//后端攻城狮
}
console.log(name)
//前端攻城狮
我们发现在不同作用域中声明并不会报错,而只有在相同作用域中重复声明变量才会报错。
const声明
- ES6中还提供了const关键字的声明。const声明指的是常量,常量就是一旦定义完就不能修改的值。还有就是常量定义时必须初始化值,如果不初始化就会报错。
- const 也是块级作用域,const常量也只会在当前代码块内有效,也不能在当前作用域中重复定义相同的变量,也不存在变量提升。
下面看例子:
//定义时必须初始化
const name = '前端攻城狮';
const age;
// Missing initializer in const declaration
//体现块级作用域
if(true){
const name = '前端攻城狮';
}
console.log(name);
// name is not defined
//体现不能重复声明
const name = '前端攻城狮';
const name = '后端攻城狮';
// Identifier 'name' has already been declared
const 声明对象
虽然 const 变量不能修改指针,但是可以修改值,比如定义一个对象,我们可以修改对象里的属性值,但是不能重写整个对象。
const person = {
name:'前端攻城狮',
age:18
};
person.age = 20;//正常
person = {};//报错,不能修改对象指针
暂时锁区
跟var相比,let和const定义变量不会被提升到作用域顶端,即便是用相对安全的typeof也会出现错误。
console.log(typeof name);
// Cannot access 'name' before initialization
let name = '前端攻城狮';
上面的例子中,console.log(typeof name)报初始化前无法访问name错误。这里是因为用let定义并初始化变量语句是不会执行的。此时的value还是处于JavaScript所谓的暂时锁区(temporal dead zone),简称为TDZ中,虽然JavaScript没有明确标准TDZ,但是人们常用它描述let和const定义的变量不会提升。
TDZ的工作原理
JavaScript引擎在扫描代码时发现变量声明时,如果遇到var就会将它们提升到当前作用域的顶端,如果遇到let或const就会将声明放到TDZ中,如果访问TDZ中的变量就会抛出错误,只有执行完TDZ中的变量才会将它移出,然后就可以正常访问。这个机制也只会在当前作用域中生效。
console.log(typeof name); //undefined
if(true){
let name = '前端攻城狮';
}
这里注意,上面这段代码是写在在js文件中,而如果在html文件中运行,会发现输出的是string。这里name由于没有声明,它默认认去window上找,而这里碰巧window对象里有name属性,并不影响以上结论。
对于let 和 var 也可以这样理解
js编译器在编译代码块的时候会把页面整个代码块的var和let的变量都找出来,并相应地在内存开辟具名空间,不同的是var在开辟空间的时候就给它的值设置了undefined称为初始化,在执行到var的那一行代码时,再给这个变量做了赋值操作。而let是执行到该行时再执行初始化而没有赋值的操作。
写在最后
var在全局作用域声明的变量有一种行为会挂载在window对象上,它会创建一个新的全局变量作为全局对象的属性,这种行为说不定会覆盖到window对象上的某个属性,而let const 声明的变量则不会有这一行为。