你了解预编译吗?几分钟带你了解它
前言
我们执行代码的过程,在 JavaScript 引擎的眼里可以分为两个重要的步骤,分别是预编译和执行。
预编译阶段会处理一些语法解析、变量声明提升等工作,为后续的代码执行做好准备;在预编辑完成后代码才开始执行。
我会用底层逻辑详细讲解代码执行的过程需要经历的过程。并且解答:
- 为什么外层作用域无法访问内层作用域,内层作用域为什么可以访问外层作用域。
- 变量声明的声明提升和函数声明的整体提升是如何实现的。
正文
我们在开始了解什么是预编译和预编译要经历的过程之前,我们需要先了解函数的自带属性、作用域和作用域连。
函数的自带属性
在JavaScript中,函数有一些自带的属性,一下是一些常见的属性:
length
:表示函数的参数个数。prototype
:指向函数的原型对象,原型对象用于定义构造函数的公共属性和方法。name
:函数的名称。arguments
:函数调用时传递的参数数组。
除了这些常见的属性外,函数还存在隐式属性,其中就包括[[scope]]
属性。[[scope]]
是 JavaScript 中函数的一个隐式属性,其中scope
翻译为域或范围。[[scope]]
属性仅供 JavaScript 引擎使用,我们无法直接访问。
在函数定义时,系统会通过scope
的内部原理定期去调用它,但不会让用户去用。当函数执行时,系统会创建一个执行期上下文的内部对象,此时[[scope]]
的值会发生变化。在函数内部访问变量时,实际访问的就是变量的scope
(作用域),scope
里有作用域链,系统会从作用域链底端依次向下去找变量。
预编译流程
我们用一个例子深入了解一下。
function a() {
function b() {
var b = 55
console.log(a);
}
var a = 200
b()
}
var glob = 50
a()
这段代码会输出什么呢?
让我们通过这段代码一起跟着JavaScript 引擎进入底层世界。
该代码大致流程:首先在全局预编译代码,然后全局执行,然后调用函数a;停止全局执行,开始预编译函数体a,预编译结束后执行函数a,最后调用函数b;停止执行函数a,预编译函数b,预编译完成后执行b;执行完b函数后返回执行a函数,a函数执行完返回全局,然后结束。
-
首先JavaScript 引擎对代码进行预编译(发生在全局中):
- 创建全局上下文对象(Global Object)用于存储全局的有效标识符。
- 在全局找变量声明,将变量名作为Global Object的属性名,属性值为undefined。
- 在全局找函数声明,将函数名作为Global Object的属性名,属性值为该函数体。
在这几个预编译的步骤中,只会在寻找变量声明和函数声明,其他语句一律跳过。按顺序依次执行完这些步骤后,我们可以得到一个Global Object。

-
对代码的预编译结束后进行全局执行。
function a() {} var glob = 50 a()
在执行到
a()
时开始调用函数,这时JavaScript 引擎会停止执行代码而去调用a函数并且对a函数进行预编译再执行。 -
在函数体a中进行预编译(发生在函数体中):
- 创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
- 在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
- 形参和实参相互统一。
- 在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。
在这几个预编译的步骤中,需要按顺序依次执行。
进行a步骤:创建一个函数上下文对象(Activation Object)

进行b步骤:在函数体里找形参和变量声明
Activation Object={
a:undefined, (形参)
a:undefined (实参)
}//是错误的
因为对象里不能存在相同的键,所以如果会进行重叠覆盖
Activation Object={
a:undefined
}
进行c步骤:形参和实参相互统一。
Activation Object={
a:undefined
}
进行d步骤:在函数体内找函数声明
Activation Object={
a:undefined,
b:function
}
-
执行函数a。
function a() { function b() { var b = 55 console.log(a); } var a = 200 b() }
当执行到
var a = 200
时Activation Object={ a:200, b:function }

当执行到b()
时调用b函数,停止执行函数a,对函数体b进行预编译。
-
在函数体b中进行预编译(发生在函数体中):
- 创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
- 在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
- 形参和实参相互统一。
- 在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。
你会发现函数预编译的方法是一样的。
我们会得到函数b的函数上下文对象为
Activation Object={ b:undefined }
-
执行函数b
Activation Object={ b:55 }

代码执行完成。
小结
预编译的具体步骤。
- 在全局进行预编译(发生在全局中):
- 创建全局上下文对象(Global Object)用于存储全局的有效标识符。
- 在全局找变量声明,将变量名作为Global Object的属性名,属性值为undefined。
- 在全局找函数声明,将函数名作为Global Object的属性名,属性值为该函数体。
- 在函数体中进行预编译(发生在函数体中):
- 创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
- 在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
- 形参和实参相互统一。
- 在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。
解答
变量声明的声明提升和函数声明的整体提升是如何实现的?
根据预编译的流程,JavaScript 引擎找到变量声明和函数声明后会在运行前赋值,分别赋值为undefined和function(函数体),然后再运行。这样就实现了变量声明的声明提升和函数声明的整体提升。
作用域和作用域链
作用域是执行期上下文对象的集合,这种集合呈链式连接,我们把这种链状关系称之为作用域链。
我们通过这个代码进行解释。
function a() {
function b() {
var b = 55
console.log(a);
}
var a = 200
b()
}
var glob = 50
a()
在这个代码中有3个作用域,分别是全局作用域和a.[[scope]]和b.[[scope]]。它们之间的关系是这样的。

这个关系是怎么形成的呢?
-
在全局预编译完成后,函数a被整体提升生成作用域,并且作用域的0号位指向Global Object。

-
在函数a预编译时:函数a的作用域的0号位指向自己的上下文对象,1号位指向Global Object;函数b在函数a的预编译过程中被整体提升,生成作用域,并且作用域的0号位指向a的作用域。

-
在函数b预编译时:函数b的作用域的0号位指向自己的上下文对象;1号位指向函数a的作用域。

通过这些步骤就可以理解作用域链是什么,怎么形成的。
解答
为什么外层作用域无法访问内层作用域,内层作用域为什么可以访问外层作用域?
因为在作用域中只能从低位向高位查找,不能从高位找回低位。

我们通过在这张图进行理解。a函数执行阶段通过作用域的0号位查找需要的有效标识符,如果没有找到便通过作用域的1号位继续查找需要的有效标识符。
小结
我们通过预编译的底层逻辑解答了
- 为什么外层作用域无法访问内层作用域,内层作用域为什么可以访问外层作用域。
- 变量声明的声明提升和函数声明的整体提升是如何实现的。
并且了解了预编译的具体步骤:
- 在全局进行预编译(发生在全局中):
- 创建全局上下文对象(Global Object)用于存储全局的有效标识符。
- 在全局找变量声明,将变量名作为Global Object的属性名,属性值为undefined。
- 在全局找函数声明,将函数名作为Global Object的属性名,属性值为该函数体。
- 在函数体中进行预编译(发生在函数体中):
-
创建一个函数上下文对象(Activation Object)用于存储函数中的有效标识符。
-
在函数体里找形参和变量声明,将形参和变量名作为Activation Object的属性名,属性值值为undefined。
-
形参和实参相互统一。
-
在函数体内找函数声明,将函数名作为Activation Object的属性名,属性值为该函数体。
-
最后我们用运行代码结尾吧
function test(a, b) {
console.log(a);
c = 0
var c;
a = 3
b = 2
console.log(b);
function b() { }
console.log(b);
}
test(1)
自己动手试试会输出什么。
转载自:https://juejin.cn/post/7362046189011435535