【JS】预编译
预编译是 js 的一个特色,也算是闭包之前最难的一个知识点。
预编译有 4 句公式,而这 4 句公式只要背熟就能理解预编译。
这 4 句公式放在本文最末尾。
本节篇幅比较长,要理解的东西也比较多。
在学习预编译之前要记住下面这句话:
JS 的声明和执行(赋值)是分开两步操作的,声明属于预编译环节。
要了解预编译,就要先了解变量声明。
变量声明又分为全局和局部。
局部变量和作用域有关,稍后会讲到。
全局变量声明要记住 2 句话
任何变量,如果未经声明就赋值,此变量就为全局变量所有。
一切声明的全局变量,都是 window 的属性。
在 console
可以看到输出的 window
里面,有变量 a
。
如果输出 window.a ,那就直接输出了 123 了。
由此可以解析上面所说的全局变量的两句话。
在全局声明的变量,称为全局变量。全局变量的作用域就是全局。
局部变量的作用域通常是在函数内部。
全局作用域称为 GO,全称:Global Object。
临时作用域成为 AO,全称:Activation Object。
而 GO 又可以理解为 window 这个对象。
GO 和 AO 要和 JS 的预编译一起理解。
预编译公式
创建 GO/AO 对象。
找形参和变量声明,将变量和形参名作为 AO 的属性名,值为 undefined。
将实参值和形参统一。
在函数体里面找函数声明,值赋予函数体。
看下面的代码,理解 GO 和 AO。
变量 a 是定义在全局上的变量。
b 是声明在全局上的函数。
所以 GO 现在就有两个值,如下图所示。
在执行这段 JS 代码前,JS 引擎会先把 JS 代码编译一遍。
这个编译的过程就是定义作用域的过程。
而最外层的作用域成为 GO,GO 里面现在就有 a 和 b,而且值都是 undefined。
上图就是完成了【预编译公式】的前两句。
由于这里是 GO,所以第三句公式是不用管的。
接下来就执行第 4 句公式。GO 就变成下图所示。
这时 b 的值就是一个函数体。
完成以上 4 句【预编译公式】后,GO 就算是定义好了。
当预编译完成后,代码会按顺序执行。
这时执行第 1 行代码。
所以 a 的值就变成 123。
第 3-5 行代码在 JS 运行时可以暂时忽略了。因为在预编译的时候已经提升了。
所以接着就运行第 7 行代码。
第 7 行代码运行后,就会执行第 3-5 行代码。由于第 3-5 行代码是一个函数体。所以这时又需要预编译一次。
函数会创建一个 AO。而且还会继承父函数的作用域下的所有东西。
下图解析
函数 b 会产生一个 AO,在这个过程中,函数 b 的 AO 首先会检查有没有形参,这里的形成是 aa。还检测到定义了一个变量 c。运行完【预编译公式】前两条,就形成上图所示的内容。
接着会执行【预编译公式】第三条:将实参值和形参统一。
所以 aa 的值这时就变成了 123(在执行函数 b 的时候,从外面传了一个值给形参 aa)。
最后在函数 b 里面寻找函数声明。由于这里没有函数声明。所以最终会形成下图所示内容。
以上就完成了函数 b 的预编译。
接下来的所有步骤都只是执行。比如执行变量 c 的赋值,最终让变量 c 的值变成 888。
以上就是 JS 的整体执行过程和预编译的原理。熟记【预编译公式】。
从这里开始就是预编译的小案例(举更多例子解析什么是预编译)
【例子 1】
上面的代码,在执行的时候(包含预编译时)会变成下图所示。
预编译公式
创建 GO/AO 对象。
找形参和变量声明,将变量和形参名作为 AO 的属性名,值为 undefined。
将实参值和形参统一。
在函数体里面找函数声明,值赋予函数体。
评论