JavaScript变量提升总结

1. 编译阶段对变量与函数声明的的处理

JavaScript引擎会在编译阶段进行数项优化,有些优化通过对代码静态分析,并预先确定所有变量和函数的定义位置,并用合适的作用域将它们关联起来,才能在执行过程中快速找到标识符。
因此,正确的思路是:包含变量和函数在内的所有声明都会在任何代码执行前首先被处理。

2. 变量提升的各种情况

2.1 正常变量使用,先声明再使用

    var a = 'test';
    console.log(a); // test
复制代码

实际执行过程中会做如下处理:

   var a; // 变量定义会提升到作用域顶部
   a = 'test' // 赋值操作会保留在原位置
   console.log(a); //test
复制代码

2.2 在变量声明之前使用变量

    console.log(a); // undefined
    var a = "test";
复制代码

如果去除变量声明

    console.log(a);  // throw Error: Uncaught ReferenceError: a is not defined
复制代码

变量使用在声明前实际执行过程中会做如下处理:

    var a;
    console.log(a); // undefined
    a = 'test';
复制代码

2.3 同名变量多次声明

    // example 1
   var a=9;
   console.log(a);//9
   var a;
   console.log(a);//9
   // example 2
   var a=9;
   console.log(a);// 9
   var a=3;
   console.log(a); // 3
复制代码

重复的var声明会被忽略,但是会保留赋值或者其他操作。

2.4 函数声明提升

   func(); //test
   function func(){
       console.log('test');
   }
复制代码

实际执行过程会做如下处理:

   function func(){
        console.log('test');
   }
   test(); // test
复制代码

2.5 重复的函数声明

   func(); // 2
   function func (){
       console.log(1);
   }
   function func(){
       console.log(2);
   }
复制代码

与变量重复声明不同,出现在后面的函数声明会覆盖前面的,实际执行过程会做如下处理:

   function func (){
       console.log(1);
   }
   function func(){
       console.log(2);
   } // 覆盖之前的函数声明
   func(); //2
复制代码

2.6 函数声明会被提升,函数表达式不会被提升

   func(); // throw Error: Uncaught TypeError: func is not a function
   var func = function(){
       console.log(1)
   }
复制代码

实际执行过程中处理如下:

   var func; // undefined
   func(); // 由于undefined不是function,函数调用操作报错
   func = function(){
       console.log(1)
   }
复制代码

2.7 变量声明与函数声明重复-函数优先

   /* example 1 */ 
   func(); // function
   var func= function(){
       console.log("variable")
   };
   function func(){
       console.log('function');
   }
   func();//variable
   
   /* example 2 */
   func(); //function
   function func(){
       console.log('function');
   }
   func(); // function
   var func= function(){
       console.log("variable")
   };
   func(); // variable
复制代码

在实际执行过程中,变量声明被忽略,但是赋值操作保留处理如下:

   function func(){
       console.log('function');
   } // 函数声明被提升
   func(); // function
   func(); // function
   /* var func; */ //变量声明被忽略 
   func= function(){
      console.log("variable")
   };
   func(); // variable
复制代码

2.8 普通块内部的变量与函数声明

   /*
   * 测试环境 
   *  1. Microsoft Edge 96.0.1054.41
   *  2. Firefox 95.0.2
   */
   // example 1
   console.log(typeof func); // undefined
   func(); // throw Error: Uncaught TypeError: func is not a function
   {
       function func() {
           console.log('a');
       }
   }
   
   // example 2
   console.log(typeof func); // undefined
   func(); // throw Error: Uncaught TypeError: func is not a function
   
   var a =true;
   if(a){
       function func() {
           console.log('a');
       }
   }else{
       function func() {
           console.log('b');
       }
   }
复制代码

在最新的浏览器中普通块内部的函数声明提升规则发生了改变,实际执行过程类似于:

   var func;
   console.log(func); // undefined
   func(); // throw Error: Uncaught TypeError: func is not a function
   {
       func = function() {
           console.log('a');
       }
   }
复制代码

2.9 内部变量、函数声明与形式参数标识符相同

   function func(inner, val) {
       console.log(typeof inner, typeof val);
       function inner() {
           console.log(111);
       }
       var val=100;
       console.log(typeof inner, typeof val);
       inner();
   }

   func('test', 'test variable');
   func(function () {
       console.log(222);
   }, function () {
       console.log(333);
   })
    /* output
    * function string
    * function number
    * 111
    * function function
    * function number
    * 111
    */
复制代码

在内部作用域声明的函数会覆盖外部函数的参数;如果内部声明的变量名称与某个参数相同,声明会被忽略掉。

3 letconst 对声明提升的影响

3.1 块作用域

   {
       let a='test';
   }
   console.log(a); // throw Error: Uncaught ReferenceError: a is not defined
复制代码

使用letconst关键字声明的变量只能在块内部使用,不能在块外部使用。

3.2 必须先声明,再使用

   let a = 1;
   function test() {
       console.log(a);
       const a = 2;
   }
   test(); // throw Error: Uncaught ReferenceError: can't access lexical declaration 'a' before initialization
复制代码

3.3 同一作用域不能存在相同标识符的变量或者函数

   // example 1
   function test(params) {
       let params = 1;
   }
   test(0);// throw Error: Uncaught SyntaxError: redeclaration of formal parameter params
   
   // example 2
   function func() {
       let test = 1;
       let test = 3;
   }
   func();// throw Error: Uncaught SyntaxError: redeclaration of let test
   
   // example 3
   function func() {
       let inner=2;
       function inner (){
           console.log(111);
       }
   }
   func();// throw Error: Uncaught SyntaxError: redeclaration of let inner
复制代码

3.4 class关键字的在声明方面类似letconst

   // example 1
   {
       class Test {
       }
   }
   var t1 = new Test();
   // throw Error: Uncaught ReferenceError: can't access lexical declaration 'Test' before initialization
   
   // example 2
   var t1=new Test();
   class Test{}
   // throw Error: Uncaught SyntaxError: redeclaration of let Test
   
   // example 3
   class Test{}
   var Test=1;
   // throw Error: Uncaught SyntaxError: redeclaration of class Test
复制代码

需要注意的点:

  1. 只有声明本身会被提升,赋值或者其他运算逻辑会留在原地,不会改变代码执行的顺序。
  2. 每个作用域都会进行提升操作。
  3. 在实际使用中要注意避免重复声明,特别是普通变量与函数声明混合在一起的时候,否则会引起很多危险的问题,推荐使用letconst声明变量。

Guess you like

Origin juejin.im/post/7050746117734023181