2

什么是变量提升?

定义:变量提升是当栈内存作用域形成时JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义

由于代码粘贴过多,附文章脉络如下图:
image.png


1. 带Var声明与不带Var声明的变量提升

区别:var操作符定义的变量会成为包含他的函数的局部变量。不过在函数内部定义变量时省略var操作符可以定义一个全局变量。

function foo() {
     a = 0;
     var b = 0;
}
  foo()
  console.log(a);//0
  console.log(b);//Uncaught ReferenceError: b is not defined

var a = b =12   // 这里的 b 也是不带 var 的。
/* 相当于
var a = 12;
b = 12
*/

var声明相关变量提升练习题--1:

console.log(a, b)
var a =12, b ='小白'
function foo(){
    console.log(a, b)
    var a = b =13
    console.log(a, b)
}
foo()
console.log(a, b)

/* 输出:
    undefined undefined
    undefined "小白"
    13 13
    12 13
*/

解释--1:

1)a和b相当于定义了两个window变量,变量在代码执行前定义但未赋值,所以此时为undefined undefined;
2)当代码执行到函数时,第一个console.log(a, b)时,函数中的局部变量a的定义被提升但是b未声明,因此作用域链当前活动对象(函数上下文)找不到b的声明,从而寻找下一个包含上下文活动对象,找到 b ='小白',所以此时打印undefined "小白";
3)当执行了var a = b =13,分别为函数局部变量a和全局变量b赋值后,此时打印的为13 13;
4)由于函数赋值的变量a为局部变量,b为全局变量,所以此时打印的a,b都是全局变量,因此a的值不变,b的值赋值为13,因此打印12 13;

var声明相关变量提升练习题--2:

  console.log(a, b)
  var a =12, b = '小白'
  function foo(){
    console.log(a, b)
  }
  foo()
  console.log(a, b)

  /* 输出:
      undefined undefined
      12 "小白"
      12 "小白"
  */

解释--2:

1)var变量声明提升到顶端,但未赋值,因此打印为undefined undefined
2)访问全局变量
3)访问全局变量

var声明相关变量提升练习题--3:

  a = 2

  function foo() {
    var a = 12;
    b = '小白'
    console.log('b' in window)
    console.log(a, b)
  }

  foo()
  console.log(b)
  console.log(a)
  /* 输出:
  true
  12 '小白'
  小白
  2
  */

解释--3:

1)b未用var操作符声明,因此默认定义未window变量,因此打印true
2)函数执行过程中当前活动对象会被压入作用域链最顶端,从作用域链中当前活动对象中寻找变量,如果找不到,才回去寻找下一个包含上下文的活动对象,因此此时打印的a为12;
3)在函数外部打印变量a,b那么当前的上下文的活动对象便是window对象,因此打印的时全局变量a,b;

var声明相关变量提升练习题--4:

  fn();
  console.log(v1);
  console.log(v2);
  console.log(v3);

  function fn() {
    var v1 = v2 = v3 = 2019;
    console.log(v1);
    console.log(v2);
    console.log(v3);
  }

  /*输出
      2019
      2019
      2019
      Uncaught ReferenceError: v1 is not defined
  */

解释--4:

1)函数声明也会提升,因此2019、2019、2019都是函数中打印的局部变量,当函数执行完毕后,window活动对象中并未定义v1、v2、v3因此此时报错。


2. 等号左边下的变量提升

= 意味着赋值,因此左边应该为变量,右边都应该是值,因此只对等号左边的进行变量提升

等号左边下的变量提升练习题--1:

  print()
  function print(){
    console.log('小白')
  }
  print()
  /* 输出:
  小白
  小白
  */

解释--1:

1)因为function关键字也会变量提升,因此不难理解相当于调用了两次print()方法,因此打印两次小白;

等号左边下的变量提升练习题--2:

  console.log(printf)//undefined
  printf()//Error
  var printf = function () {
    console.log('林一一')
  }
  printf()//未执行
  /*输出
      undefined
      Uncaught TypeError: print is not a function
  */

解释--2:

1)同样由于变量提升机制带 var 的 print 是一开始值是 undefined
2)由于1),并且此时并未给printf赋值成函数,所以 print() 这时还不是一个函数,所以报出 类型错误TypeError

等号左边下的变量提升练习题--3:

  var fn = function sum() {
    console.log(sum);//=>函数本身
    console.log(1);//=>1
  }
  // sum();//=>Uncaught ReferenceError: sum is not defined
  fn();
  /* 输出:
  ƒ sum() {
    console.log(sum);//=>函数本身
    console.log(1);//=>1
  }
  
  1
   */

解释--3:

1)sum()是匿名函数具体化后的名字,只可以在函数上下文中使用,因此包含上下文中访问报Error;
2)fn定义提升并初始化为undefined后,执行到sum()为fn重新赋值,在调用sum可以正常输出。


3. 条件判断下的变量提升

当前作用域不管条件是否成立都要进行变量提升 ,function在老版本浏览器 (IE10以下)的渲染机制下,声明和定义都处理,但是为了迎合es6中的块作用域, 新版本浏览器对于在条件判断中的function不管条件是否成立,都只是先声明,没有定义 类似于var ,同时判断体中如果出现了let、const、function会创建一个新的执行上下文即块级上下文。

条件判断下的变量提升练习题(变量和函数提升的区别)--1:

  console.log(a); // undefined
  console.log(b); // undefined
  if (true) {
    console.log(a); // undefined
    var a = 1;
    console.log(b); // ƒ b() {console.log("1");}
    function b() {
      console.log("1");
    }
  }
  console.log(a); // 1
  console.log(b()); // 1,undefined
  /* 输出:
  undefined
  undefined
  
  ƒ b() {
      console.log("1");
    }
    
  1
  1
  undefined//因为函数b()没有返回值,因此console.log(b()),b()执行时打印1,执行完毕打印undefined
  */

解释--1:

1) 在当前作用域下不管条件是否成立都要进行变量提升,为了迎合es6中的块作用域, 新版本浏览器对于在条件判断中的function不管条件是否成立,都只是先声明,没有值。因此,a、b打印的都是undifined。
2)在执行到if(){}语句块中,变量执行到赋值语句才赋值,而函数直接赋值
3)if执行结束,赋值操作结束,因此可以打印a,b();

条件判断下的变量提升练习题(变量提升)--2:

  console.log(a)
  if(true){
    console.log(a)
    var a = '小白'
  }
  console.log(a)
/* 输出
    undefined
    undefined
    小白
/

解释--2:

1)无论条件是否成立,都会进行变量提升,因此打印undefined;
2)赋值后重新打印a;

条件判断下的变量提升练习题--3:

  // console.log(f)//Uncaught ReferenceError: f is not defined
  if(function f(){}){
    console.log(typeof f)//undefined
  }

解释--3:

1)if 中 () 内的表达式不会变量提升;
2)判断的条件没有提升,所以条件内部的 f 是未定义

仔细对比如下代码:

  console.log(globalPrint)//ƒ globalPrint(){}
  console.log(fatherPrint)//undefined
  console.log(childPrint)//undefined
  function globalPrint(){}
  if(true) {
    console.log(globalPrint)//ƒ globalPrint(){}
    console.log(fatherPrint)//ƒ fatherPrint() {}
    console.log(childPrint)//undefined
    function fatherPrint() {}
    if(false){
      console.log(globalPrint)//
      console.log(fatherPrint)//
      console.log(childPrint)//
      function childPrint(){
        console.log(1)//
        return "childPrint()的返回值";
      }
    }
  }
  console.log(childPrint)//undefined
  console.log(globalPrint)//ƒ globalPrint(){}
  console.log(fatherPrint)//undefined
  console.log(childPrint)//undefined
  function globalPrint(){}
  if(true) {
    console.log(globalPrint)//ƒ globalPrint(){}
    console.log(fatherPrint)//ƒ fatherPrint() {}
    console.log(childPrint)//undefined
    function fatherPrint() {}
    if(true){
      console.log(globalPrint)//ƒ globalPrint(){}
      console.log(fatherPrint)//ƒ fatherPrint() {}
      console.log(childPrint)//ƒ childPrint(){console.log(1)return "childPrint()的返回值";}
      function childPrint(){
        console.log(1)//1
        return "childPrint()的返回值";
      }
    }
  }
  console.log(childPrint())//childPrint()的返回值

总结:

  • if()判断语句中声明的方法无论在全局上下文或函数的执行上下文中,函数都无法提升。
  • if{}语句块中,var变量和函数都会作用域提升,但是函数赋值是在进入语句块之后,而变量赋值是在执行赋值语句后。
  • 函数变量会提升,但是只有代码执行到函数声明的语句块时才会立即赋值,因此父级上下文访问子上下文中的函数时为undefined(缺省值:声明了但未赋值)。

    4. 重名问题下的变量提升

    函数提升优先级比变量提升优先级高,而且不会被变量声明覆盖,但是会被变量赋值覆盖

    重名问题下的变量提升练习题(变量提升)--1:

    console.log(a);//ƒ a(){console.log(1);}
    var a=1;
    console.log(a)//1
    function a(){
      console.log(1);
    }
    console.log(a)//1
    
    /* 输出:
    ƒ a(){console.log(1);}
    1
    1
    */
    

    解释--1:

    1)因为函数提升优先级高于变量提升优先级,因此第一个console.log(a)打印的是函数体,又因为在全局上下文中,因此打印的不是undefined;
    2)因为在函数a和变量a的提升后,执行了a=1的赋值语句,因此函数被覆盖,所以第二个console.log(a)打印的是1;
    3)第三个console.log(a)也是因为这原因。

    重名问题下的变量提升练习题(变量提升)--2:

    var fn = 12
    function fn() {
      console.log('林一一')
    }
    console.log(window.fn)//true
    fn()//fn为12,因此Uncaught TypeError: fn is not a function

    重名问题下的变量提升练习题(变量提升)--3:

    变量重名在变量提升阶段会重新赋值

    console.log(a);//undefined
    var a=1;
    console.log(a)//1
    if(true){
      console.log(a())//2 undefined
      function a(){
        console.log(1);
      }
      function a() {
        console.log(2)
      }
    }

    解释--2:

    1)if()语句块中的函数a提升后,同名的var a提升在函数变量后面;此时console.log(a)访问到的第一个a是函数,但是此时函数未赋值,因此打印为undefined;
    2)执行到赋值语句a=1后,a(赋值的还是函数a,也就是第一个提升的a变量)被赋值为1,因此打印为1;
    3)当执行到if(){}语句块中,相当于对a()声明赋值两次,后一次覆盖前一次,因此函数a():function a() {console.log(2)},又由于函数无返回值,因此打印一个2,又打印一个undefined;

    重名问题下的变量提升练习题(变量提升)--4:

    console.log('1',fn())
    function fn(){
      console.log(1)
    }
    
    console.log('2',fn())
    function fn(){
      console.log(2)
    }
    
    console.log('3',fn())
    var fn = '林一一'
    
    console.log('4',fn())
    function fn(){
      console.log(3)
    }
    /* 输出
     3
     1 undefined
     3
     2 undefined
     3
     3 undefined
     Uncaught TypeError: fn is not a function//由于赋值语句,因此赋值后的fn不再是一个函数
    /

5.函数形参的变量提升

  function a(b){//函数形参的变量提升过程
    //var b;
    //b = 45;
    console.log(b);//45;
  }
  a(45);

函数形参的变量提升练习题--1:

  function foo(a) {
    console.log(a)//0
    console.log(b)//undefined
    var a=1,b;
    console.log(a)//1
  }
  foo(0);

解释--1:

1)虽然函数内部又定义了一次变量a,但是形参传入过程中相当于已经定义赋值一次,因此打印出来的a为传入的参数,再次执行赋值语句后,a的值发生变化;

函数形参的变量提升练习题--2:

  var a = 1;
  function foo(a, b,c) {
    console.log(a); // 1
    a = 2;
    arguments[0] = 3;
    var a;
    console.log(a, this.a, b); // 3, 1, undefined
  }
  foo(a);

解释--2:

1)这道题中,只需要注意函数形参已经声明,赋值,只能重新赋值覆盖,不可能出现undefined情况。其中arguments[0]是将传入的参数a赋值为3,this指针只想window;

6.自执行函数的变量提升

立即调用的匿名函数又被称作立即调用的函数表达式(IIFE),它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式,位于IIFE中的代码在其外部是无法访问的

  var a = 10;
  var b = 10;
  (function a() {
    console.log(a)//函数体
    console.log(b)//10
    a = 20
    b = 20
    console.log(a)//函数体
    console.log(b)//20
  })()
  console.log(a)//10
  console.log(b)//20

参考资料:

理解尚浅,望不吝赐教!

很白的小白
145 声望125 粉丝