JavaScript变量提升

什么是变量提升

当栈内存的作用域形成时,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声明的函数等等。

分析

  1. 找到所有的非函数中的var声明
  2. 找到所有的顶级函数声明(不在大括号内的函数声明)
  3. 找到顶级let,const,class声明
  4. 找到块中的声明,函数名不与上述重复

判断是否有重复

只要是使用了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();

你可能感兴趣的:(JavaScript,javascript,前端)