语句的执行过程
整理的笔记,来源详见文末
语句是任何编程语言的基础结构,比较常见的有变量声明、表达式、条件、循环等。与 JavaScript 对象一样,JavaScript 语句同样具有“看起来很像其它语言,但是其实一点都不一样”的特点。
JavaScript 语句的执行机制涉及一种基础类型:Completion 类型,它用于描述异常和跳出等语句执行过程。这一机制的基础正是 JavaScript 语句执行的完成状态,我们用一个标准类型来表示 Completion Record,它表示一个语句执行完之后的结果,有三个字段:
[[type]]
表示完成的类型,有 break, continue, return, throw 和 normal 几种类型[[value]]
表示语句的返回值,如果语句没有,则是 empty[[target]]
表示语句的目标,通常是一个 JavaScript 标签
JavaScript 正是依靠语句的 Completion Record 类型,来控制语句的执行过程,即便有复杂的嵌套。
1. 普通语句
在 JavaScript 中,普通语句就是不带控制能力的语句。它有以下几类:
声明类语句:var, const, let, function, class 声明
声明类语句会响应预处理过程,其它普通语句只有执行过程
表达式语句
只有表达式语句会产生
[[value]]
空语句
debugger 语句
with 语句(已废弃)
这些语句在执行时,是从前到后顺次执行的(先忽略 var 和函数声明的预处理机制),没有任何分支或重复执行的逻辑。普通语句执行后,会得到 [[type]]
为 normal 的 Completion Record,JavaScript 引擎会继续执行下一条语句。
表达式语句
普通语句中,只有表达式语句会产生 [[value]]
。 我们常用的 Chrome 开发者工具,它控制台显示的就是语句的 Completion Record 的 [[value]]
。
控制台的输入如下:
表达式语句实际上就是一个表达式,它是由运算符连接变量或者直接量构成的。
Primary Expression,主要表达式。表达式的最小单位,它所涉及的语法结构也是优先级最高的
MemberExpression,成员表达式。
NewExpression,new 表达式。
CallExpression,函数调用表达式。
LeftHandSideExpression,左值表达式。
AssignmentExpression,赋值表达式。
Expression,表达式,用逗号运算符连接的赋值表达式。
表达式的右边部分
2. 语句块
语句块就是用 {}
括起来的一组语句,可以嵌套。
语句块本身并不复杂,需要注意的是,当语句块的内部语句的 Completion Record 的 [[type]]
不为 normal 时,会打断语句块后续的语句执行。
比如,这是一个内部为普通语句的语句块:
当我们在第三行加入一条 return 语句时,整个语句块就变成了非 normal 的,它会打断语句块中后续语句的执行,以此保证非 normal 的完成类型可以穿透复杂的语句嵌套结构,从而产生控制效果。
3. 控制型语句
控制型语句会对不同类型的 Completion Record 产生反应。
对内部产生影响的
条件语句:if, switch
循环语句:for, while
for, for...in, for...of, for-await-of
while, do-while
try
try 部分用于标识捕获异常的代码段
catch 部分则用于捕获异常后做一些处理
finally 则是用于执行后做一些必须执行的清理工作
对外部产生影响的:continue, break, return, throw
为了保证语义清晰,不建议用 throw 表达任何非异常逻辑
这两类控制型语句的配合,会产生控制代码执行顺序和执行逻辑的效果,这也是我们编程的主要工作。通常情况,我们熟悉的是 for/while + break/continue 和 try + throw 这样的组合。但是实际上,这两类是可以两两组合的。如下:
比如下面的两个示例:
因为 finally 中的内容必须保证执行,所以 try/catch 执行完毕,即使得到的结果是非 normal 型的完成记录,也必须要执行 finally。而当 finally 执行也得到了非 normal 记录,则会使 finally 中的记录作为整个 try 结构的结果。
4. 带标签的语句
任何 JavaScript 语句都是可以加标签的,在语句前加冒号。
大部分时候,这个东西类似于注释,没有任何用处。唯一有作用的时候,是和 Completion Record(完成记录)类型中的 [[target]]
配合,用于跳出多层循环。比如:
break/continue 语句后,如果跟了关键字就会产生带 [[target]]
的完成记录。一旦完成记录带了 [[target]]
,那么只有拥有对应 label 的循环语句会消费它。
5. 写在最后
JavaScript 中语句的执行过程,是由 Completion Record 类型来控制的。
因为语句之间存在着嵌套关系,所以语句的执行过程实际上主要是在一个树形结构上进行的。 树形结构的每一个节点在执行后都会产生一条 Completion Record,然后根据语句的结构和 Completion Record,JavaScript 实现了各种分支和跳出逻辑。
6. 参考
Last updated