这一次要彻底搞清JS提升

1,260 阅读4分钟

前言

我们知道JavaScript有预编译环节,这个环节发生在引擎编译代码前,用于做一些代码的“拼凑”工作,方便之后执行代码。预编译往往导致两个奇怪的现象:变量声明提升和函数整体提升。当然这两句话只是简单的总结,不能解决所有问题。

今天看了《你不知道的JS》上卷第四章,让我对“提升”有了更加深刻的理解。所以我将这次理解的加上以前对JS提升的认知写成此文

首先,来看两个简单的例子

首先,来看个简单的例子,函数fn会被执行吗?

fn();
function fn(){
  console.log("fn");
}

再看一个复杂一点点的例子,下面代码打印的结果是啥?

function fn(){
    return f;
    f = 10;
    function f(){
        var f = 11;
    }
}
console.log(typeof fn());

第一个答案是:会执行,打印出 fn ; 第二个答案是:function

如果答对了,那恭喜你,说明你对预编译有了较深的理解!如果答错了也别灰心,带着你的疑问接着往下看,这篇文章会给你解答

理解什么是提升

关于什么是提升,我还是喜欢先从字面意思理解,“提”是提起、拎起的意思,“升”是升起、向上移动的意思,所以提升大概就是向上移动的意思。 在JS中,要理解提升,我觉得先要理解一下几点:

  1. 提升的东西是什么,把要提升的东西提升到哪里去?
  2. 为什么要有提升,有什么好处?
  3. 提升的原理及具体过程是怎样的?

下面分别来讲:

提升的东西是什么,提升到哪

之前我写过一文关于引擎、编译器和作用域,里面提到:

引擎会在解释JavaScript代码之前首先对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

这个机制也正是词法作用域的核心内容,用一句话来概括这两个问题,就是编译器会将所有的函数和变量声明提升到各自作用域的最顶端。 因此,我们回到第一个题目

fn();
function fn(){
  console.log("fn");
}

由于函数会在代码执行前首先被处理,即将函数提升到所在作用域的最顶端;因此fn函数执行时,是可以访问到的,因此打印fn

提升的好处

提升的好处简单来说就是方便引擎执行JavaScript代码时,可以更方便的查找变量

JS提升的详细过程

参考了之前的学习笔记并从新整理,我将JS提升的详细过程总结为一下四步:

  1. 创建AO对象;(Active Object,执行期上下文)
  2. 找形参和变量声明,将变量名和形参名作为AO对象的属性名,值为undefined
  3. 将实参和形参相统一;
  4. 在函数体里面找函数声明(不包括函数表达式),将函数声明的名作为AO对象的属性名挂起,值为该函数。

这个过程需要注意一下几点

  1. 函数和变量都会提升,但是变量是声明提升,而函数是整体提升,并且函数优先
  2. 函数表达式不会被提升
  3. 每个作用域都会进行提升,包括全局作用域

有了以上知识的铺垫,我们来看第二道题

function fn(){
    return f;
    f = 10;
    function f(){
        var f = 11;
    }
}
console.log(typeof fn());

fn函数执行时,里面fn的作用域大致是: (记住,为什么我说是大致?)

//fn执行前的作用域的伪代码
AO : {
  f : function f(){ var f = 11}
}

fn执行时,里面第一条就是return f,所以fn的执行结果是就f函数,因此typeof fn()的结果就是"function"

解决了两个问题,我们再来闲聊我刚刚为什么说是大致?换句话说我们来聊聊作用域的本质,作用域里头还有什么?

作用域本质是执行期上下文,一个执行期上下文定义了一个环境执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文会被销毁。

通常执行上下文都会呈链式链接,我们叫做作用域链。作用域链在JavaScript中的具体表示形式是[[scope]][[scope]]指的就是我们所说的作用域链,该属性仅供JavaScript引擎存取调用。在函数作用域中,不仅有上下文AO,还有thisarguments等。全局作用域会更多,但为了方便,一般在分析作用域时,那些我们不关系的就不会考虑写出来。

以上就是关于JS提升个人总结

最后在出一道题来检测你的学习成果

var a = 1;
function a(a){
    console.log(a)
    var a = 2
}
console.log(a)
a(3)

先给出你的答案,答案之后会在评论区出现

END