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
【从【预编译】到【声明提升】到【作用域链】再到【闭包】】
文章图片
推荐阅读
- 选择篇(022)-下面代码的输出是什么?
- 从0到1搭建组件库
- JS中 &&、|| 和 & 、| 的使用
- Mac 上制作 SSL 证书
- JavaScript 基本数据类型转换
- 微信商城小程序开发方式有哪些()
- Form 表单在数栈的应用(下)(深入篇)
- 选择篇(019)-下面代码的输出是什么?
- 选择篇(020)-下面代码的输出是什么?
- 选择篇(018)-下面代码的输出是什么?