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