什么是变量提升
当栈内存的作用域形成时,js代码执行前浏览器将带有var关键字的变量提前声明(也就是在变量所属的作用域的顶部声明,虽然声明了,但是没有定义,在赋值前——也就是写着var XXX的地方之前,值为undefined),将带有function关键字的变量提前进行声明和定义(就是说带function的只要在这个作用域内写了,就可以用,哪怕调用这个函数的地方在他前面)。当js代码执行的时候,遇到创建函数的代码会直接跳过。
例如:
console.log(a,b); //undefined undefined
console.log(sum(1,2)); //3
var a = 1
var b = a
console.log(a,b); //1 1
b = 2
console.log(a,b); //1 2
function sum(x, y) {
var total = x + y
return total
}
console.log(sum(1,2)); //3
console.log(add(1,2)); //add is not a function
var add = function(x,y) {
var total = x + y
return total
}
执行上下文
执行上下文的生命周期有三个阶段:创建阶段、执行阶段、销毁阶段。与变量提升关系最密切的是创建阶段。
创建执行上下文
在执行全局代码前,会创建一个全局执行上下文,并对全局数据进行预处理。
在这一阶段会进行变量和函数的初始化声明,比如var与function的变量提升并添加为window属性,this赋值windows。
同样,在调用函数前,也会创建一个函数执行上下文对象。比如var定义的局部变量,function声明的函数等等。
分析
- 找到所有的非函数中的var声明
- 找到所有的顶级函数声明(不在大括号内的函数声明)
- 找到顶级let,const,class声明
- 找到块中的声明,函数名不与上述重复
判断是否有重复
只要是使用了let,const,class声明的,命名都得是独一无二的,但是var和function声明的变量和函数,名字是可以重复的。倘若var与function声明的名字相同,function优先。
var的作用
带var是在这个函数作用域内声明了一个变量,不带var则会向上级作用域查找,相当于使用了上级作用域的变量,如果找到window还没找到,就相当于给window设置了一个变量。
var a = 10;
function show() {
console.log(a); //undefined
var a = 2;
console.log(a); //2
}
show();
console.log(a); //10
在函数show()里,由于变量提升,因此在函数最开始就进行了var a 这一操作,所以函数内的第一个输出不会向上级作用域查找,输出为undefined。第二个输出在输出前,已经对a进行了赋值,因此输出结果为2.当函数结束后,show()函数的作用域被释放,因此第三个输出结果为10.
fun();
console.log(a);
console.log(b);
console.log(c);
function fun(){
var a = b = c = 1;
console.log(a);
console.log(b);
console.log(c);
}
//1 1 1 a is not defined
函数fun()内,赋值的时候相当于
var a = 1;
b = 1;
c = 1;
因此当函数执行完之后,在全局作用域内找不到a,但是可以找到b和c
fun();
console.log(b);
console.log(c);
function fun(){
var a = b = c = 1;
console.log(a);
console.log(b);
console.log(c);
}
//1 1 1 1 1
条件语句中的变量提升
在if...else...条件语句中,不论条件是否成立,都会进行变量提升。
console.log(a)
if(false){
var a = 1
}
console.log(a)
/* 输出
undefined
undefined
/
虽然条件不成立,但是a进行了变量提升,但又因为条件不成立,因此没有给a赋值,所以在条件语句外输出a为undefined。
需要注意的是,在条件语句中,function定义的虽然会进行变量提升,但是此时只进行了声明,只有当条件成立之后,才会进行定义。
重名时的变量提升
需要注意,js在执行上下文中会检测命名重复,let,const,class声明的名字之间不能重复,let,const,class和var,function的名字不能重复,但是var和function命名可以重复,当var和function命名重复的时候,情况如下:
如果一个var声明的变量和一个function声明的函数命名相同,var声明的变量会首先进行变量提升,但是之后会被function声明的函数所覆盖。但是在执行代码时,js顺序执行,执行了给a赋值12的操作,因此打印出来a是12,在接下来的a(),此时a不是一个函数,因此报错。
console.log(typeof(a)); //function
var a = 12
function a() {
console.log(111)
}
console.log(a) //12
a() //a is not a function
暂时性死区
当我们使用let来定义变量的时候,并非没有提升,但是此提升非彼提升,它和var的变量提升并不相同,js变量有创建、初始化、赋值三个步骤。var的变量提升,是在本作用域的开端,创建了变量,并对其进行初始化,初始化为undefined。当我们用let来定义变量的时候,同样会在本作用域的开端创建变量,但是并没有进行初始化,用let定义的变量初始化这一步骤是在当js执行代码执行到写有let a = 0(例子)的时候进行的,let a = 0就是将a初始化为0。因为这样,let定义变量的时候就产生了暂时性死区的情况。
let a = 0;
function arr(){
console.log(a); //0
}
arr();
let a = 0;
function arr(){
console.log(a); //报错
let a = 1;
console.log(a);
}
arr();