📰 来源: 博客园
1. 前文总结 和 运行期前置知识
这个系列文章,已经写了一少半了,现在终于到了动态执行阶段了。
我们首先需要梳理一下知识,这部分内容,相对独立,但是都算是比较重要的知识点。
预编译的说法为什么不建议使用
在我们平时看文章,看资料,甚至是看一些比较权威的文档时,预编译 这个术语非常常见。但是,在js中,预编译 是个伪术语,是一些教材教程在以前的js教学中,为了解释变量提升等一些问题,生造出来的一个词语,后来,只要是运行期以前的 甚至是在和运行期交织发生的一些动作流程,统统装进了 预编译 这个大口袋里。大部分人,也就不求甚解的接受并使用了这个说法。但是,这是一个不规范且容易引发歧义的词汇。在传统编译语言中,预处理、编译与执行通常有明确的时间边界;在现代 JavaScript 环境,这些阶段高度交织。规范(ECMAScript (ECMA-262))并不使用“预编译”一词,而是通过“执行上下文的创建阶段(creation / declaration instantiation)”来描述声明的注册与初始化。实际引擎(例如 V8)则采用惰性解析与按需编译:先做必要的解析与作用域分析,再由解释器生成字节码(如 Ignition)或在运行时将热点编译为机器码(由优化器完成)。
对于js,可以分为如下四个宏观的阶段:
词法分析:把源代码分成记号(tokens)。
语法分析(Parsing):构建抽象语法树(AST),确定静态作用域结构。
执行上下文创建阶段(Creation / Declaration Instantiation):为全局或每次函数调用登记标识符(函数声明整体被绑定;var 注册并初始化为 undefined;let/const 注册但处于 TDZ)。这一步决定了变量可见性和提升行为,但不等于把所有代码预先编译成机器码。
执行阶段:逐条执行语句;遇到函数调用重复执行上一 步。现代引擎会在此阶段对运行行为收集反馈,并按需触发优化编译。
预编译的说法为什么不建议使用
在我们平时看文章,看资料,甚至是看一些比较权威的文档时,预编译 这个术语非常常见。但是,在js中,预编译 是个伪术语,是一些教材教程在以前的js教学中,为了解释变量提升等一些问题,生造出来的一个词语,后来,只要是运行期以前的 甚至是在和运行期交织发生的一些动作流程,统统装进了 预编译 这个大口袋里。大部分人,也就不求甚解的接受并使用了这个说法。但是,这是一个不规范且容易引发歧义的词汇。在传统编译语言中,预处理、编译与执行通常有明确的时间边界;在现代 JavaScript 环境,这些阶段高度交织。规范(ECMAScript (ECMA-262))并不使用“预编译”一词,而是通过“执行上下文的创建阶段(creation / declaration instantiation)”来描述声明的注册与初始化。实际引擎(例如 V8)则采用惰性解析与按需编译:先做必要的解析与作用域分析,再由解释器生成字节码(如 Ignition)或在运行时将热点编译为机器码(由优化器完成)。
对于js,可以分为如下四个宏观的阶段:
词法分析:把源代码分成记号(tokens)。
语法分析(Parsing):构建抽象语法树(AST),确定静态作用域结构。
执行上下文创建阶段(Creation / Declaration Instantiation):为全局或每次函数调用登记标识符(函数声明整体被绑定;var 注册并初始化为 undefined;let/const 注册但处于 TDZ)。这一步决定了变量可见性和提升行为,但不等于把所有代码预先编译成机器码。
执行阶段:逐条执行语句;遇到函数调用重复执行上一 步。现代引擎会在此阶段对运行行为收集反馈,并按需触发优化编译。
全局创建阶段和函数创建阶段的区别
无论是全局还是函数,在代码真正执行前都会经历“创建阶段”(进行变量和函数声明的提升),但两者有本质区别:
作用域范围:
-
全局阶段:影响整个程序,声明的变量和函数最终挂载到全局环境(浏览器中为
window)。 -
函数阶段:每调用一次函数,生成一个完全独立的执行上下文,仅对函数体内部有效,互不干扰。
变量遮蔽(shadowing):
- 在函数内部,如果存在与全局同名的变量,函数内的局部变量会“遮蔽”全局变量。即使全局变量在早期的全局阶段已经存在,函数内部在自己的创建阶段会优先登记局部标识符。
全局创建阶段和函数创建阶段的区别
无论是全局还是函数,在代码真正执行前都会经历“创建阶段”(进行变量和函数声明的提升),但两者有本质区别:
全局阶段:影响整个程序,声明的变量和函数最终挂载到全局环境(浏览器中为 window)。
全局阶段:影响整个程序,声明的变量和函数最终挂载到全局环境(浏览器中为 window)。
函数阶段:每调用一次函数,生成一个完全独立的执行上下文,仅对函数体内部有效,互不干扰。
函数阶段:每调用一次函数,生成一个完全独立的执行上下文,仅对函数体内部有效,互不干扰。
变量遮蔽(shadowing):
四个宏观的阶段
JavaScript 代码的完整生命周期分为以下四个阶段:
1. 词法分析(Lexical Analysis)
- 目的:将源代码字符串分解成一系列记号(Tokens)。
- 内容:识别关键字、标识符、操作符、数字、字符串、注释等最小语法单元。
2. 语法分析(Syntax Analysis / Parsing)
- 目的:将记号序列转换成抽象语法树(AST)。
- 内容:检查代码结构是否符合语法规则,构建反映代码静态结构的蓝图。
3. 执行上下文创建阶段(Creation/Instantiation Phase)
- 全局上下文创建:
- 创建全局对象(Global Object)。
- 扫描全局代码:将函数声明整体提升;将
var变量注册并初始化为undefined;将let/const注册,但置于“暂时性死区(TDZ)”。 - 建立全局词法环境,其外部引用为
null。 - 计算
this绑定。
- 函数上下文创建(每次调用时触发):
- 确定外部环境引用(Outer Environment Reference),构建作用域链。
- 创建局部词法环境,绑定形参与实参,创建
arguments对象。
🔗 原文链接: 点击阅读原文
文章评论