《You Dont Know JS上卷》(四) ---提升

61 阅读3分钟

提升

提升指的是: 包含变量和函数的所有声明都会在任何代码被执行前首先被处理.这个过程就好像变量和函数的声明从他们的代码中被移动到最上面了

看一下这个例子:

console.log(a) //输出undefined
var a = 2

由于变量声明在函数执行前被提前处理,移动至最上面,因此实际的代码执行顺序是这样:

var a;
console.log(a);
a = 2;

变量为什么会提升

回忆一下第一章节内容,源代码转换为机器指令要先进行编译,而编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来,这也是第二章词法作用域的主要内容,

上述例子中值得注意的是: var a = 2被拆解为两个部分: var a;a = 2,第一部分是在编译阶段进行的,第二部分赋值会留在原地等待执行阶段.也就是说只有声明会被先处理(提升),而赋值等其他逻辑会留在原地等待执行

函数优先

函数要比变量优先提升,就是说先提升所有的函数,然后再提升变量

foo(); //输出undefined
var foo = 1;
var b = 2;
function foo() {
  console.log(b);
}

看上述代码其执行过程是这样的:

  1. 代码首先进入编译阶段: 对所有声明进行提升,优先提升函数:

    1. 编译器发现有函数声明,于是先把函数声明提升至作用域最顶端

    2. 之后发现了又一个声明var foo,编译器会询问作用域里是否已存在这个声明,作用域说:之前已经有了一个foo的函数声明,编译器发现作用域中已经存在了foo的声明,就会忽略掉这个次的var foo

    3. 全部编译完后,代码顺序变成了这个样子:

      function foo(){...}//提升了的函数声明,注意只是声明被提升了,而实际的函数体没有,执行时会根据函数声明找到函数体
      var b;
      foo();              //执行函数
      foo = 1;            //将foo重新赋值为1
      b = 2               //给b赋值
      
  2. 执行阶段: 由上至下开始执行编译后的代码

需要注意的是: 在普通的块中函数声明仍然会被提升至所在作用域顶部,猜猜以下代码输出什么?

foo()
var a = 0;
if(a){
    function foo(){
        console.log("a")
    }
}else {
    function foo(){
        console.log("b")
    }
}

输出: TypeError: foo is not a function,你可能会疑惑为什么输出的不是ReferenceError 引用错误而是TypeError 类型错误,其主要原因是: 普通块内的函数声明会被提升至所在作用域顶部,那么foo的声明就提升到了foo()之前,由于还未执行到if语句中,所以并未给foo赋值,此时foo的值为默认值undefined,当执行foo()时,其实是将undefined当作函数调用