JavaScript 引擎在代码执行之前会先进行如下操作:
- 先进行
分词 / 词法剖析
将语句宰割成词法单元token
,在对以后的整个作用域剖析实现后,JS 引擎会将token
进行解析 / 语法分析
翻译成AST
(形象语法树) - 预编译(预处理)
- 边解释边执行(不是纯解释,还有 JIT 编译,这里不开展了)
预编译(预处理)
有人说 JavaScript 没有预编译,是属于 语法分析
的一部分,有人说有 词法, 语法和代码生成
就曾经属于编译了,只是不须要提前编译的,而是在执行前的几毫秒才编译。所以 JavaScript 到底有没有预编译或者这些步骤属不属于预编译(预处理)大佬们的意见也不同,咱们就不探讨了,暂且把语法分析生成 AST 后到执行前的一些处理过程称之为 “预编译”
把。
-
全局预编译
- 创立
Global Object
全局上下文对象 - 找变量申明,将变量名绑定为
GO
的一个属性,值为undefined
- 找函数申明,将函数名绑定为
GO
的一个属性,值为函数体
- 执行全局代码
- 创立
-
部分预编译
- 创立
Activation Object
函数上下文对象 - 找行参,将行参名绑定为
AO
的一个属性,值为undefined
- 找变量申明,将变量名绑定为
AO
的一个属性,值为undefined
- 实参值赋值给形参
- 找函数申明,将函数名绑定为
AO
的一个属性,值为函数体
- 函数生成
[[scope]]
属性,值为一个数组,数组的第一项是本函数的 AO, 下一项为外层函数的 AO,始终往外层,直到最初一项为 GO - 执行函数代码
- 创立
申明晋升,作用域链
下面预编译的前半部分就是所谓的 申明晋升
,我有文章独自讲了申明晋升就不赘述了,详情点击这里。
函数执行前会生成 `[[scope]]` 属性,值为一个数组,数组的第一项是本函数的 AO
下一项为外层函数的 AO,始终往外层,直到最初一项为 GO
AO
是函数的作用域,而 [[scope]]
这样的链式数组则是函数的作用域链,当函数进行 值查问 RHS
时会先在 [[scope]]
中的第一项中寻找,如果没有找到则查找第二层,以此类推直到查找到 GO 为止。
闭包
当 函数 FN
执行完结后,这个 函数 FN
的 AO
的生命周期就应该完结了,须要被销毁。然而有非凡状况,尽管说是非凡状况,然而开发中十分常见,就是 函数 FN
执行完结后 return
了一个 外部函数 A
并被内部保留后,原本应该被销毁的 函数 FN
的 AO
便无奈被销毁,因为函数 A 的 [[scope]]
保留着 函数 FN
的 AO
作为本人原型链的一部分,只有保留 函数 A
的变量不被销毁或者变量所在的 AO
不被销毁则 函数 FN
的 AO
永远不会被销毁,并且变量保留的 函数 A
能够始终拜访 函数 FN
中的变量和值,这样的景象叫做 闭包
,这样的闭包大量呈现会导致内存透露或者加载过慢。
var Variable_1 = 1;
function FN() {
var Variable_2 = 2;
function A() {
var Variable_3 = 3
console.log(Variable_2);
}
return A;
}
var Function_A = FN();
FnVariable(); // 承受 函数 A 的变量 Function_A 通过调用能够输入 FN 中的 Variable_2