# 语句的执行过程

语句是任何编程语言的基础结构，比较常见的有变量声明、表达式、条件、循环等。与 JavaScript 对象一样，JavaScript 语句同样具有“看起来很像其它语言，但是其实一点都不一样”的特点。

JavaScript 语句的执行机制涉及一种基础类型：[Completion 类型](https://anjia1.gitbook.io/web/js/data-type/specification-type)，它用于描述异常和跳出等语句执行过程。这一机制的基础正是 JavaScript 语句执行的完成状态，我们用一个标准类型来表示 Completion Record，它表示一个语句执行完之后的结果，有三个字段：

* `[[type]]` 表示完成的类型，有 break, continue, return, throw 和 normal 几种类型
* `[[value]]` 表示语句的返回值，如果语句没有，则是 empty
* `[[target]]` 表示语句的目标，通常是一个 JavaScript 标签

JavaScript 正是依靠语句的 Completion Record 类型，来控制语句的执行过程，即便有复杂的嵌套。

## 1. 普通语句

在 JavaScript 中，普通语句就是不带控制能力的语句。它有以下几类：

1. 声明类语句：var, const, let, function, class 声明
   * 声明类语句会响应<mark style="color:red;">预处理过程</mark>，其它普通语句只有执行过程
2. 表达式语句
   * 只有表达式语句会产生 `[[value]]`
3. 空语句
4. debugger 语句
5. with 语句（已废弃）

这些语句在执行时，是从前到后顺次执行的（先忽略 var 和函数声明的预处理机制），没有任何分支或重复执行的逻辑。普通语句执行后，会得到 `[[type]]` 为 normal 的 Completion Record，JavaScript 引擎会继续执行下一条语句。

### 表达式语句

普通语句中，只有表达式语句会产生 `[[value]]`。 我们常用的 Chrome 开发者工具，它控制台显示的就是语句的 Completion Record 的 `[[value]]`。

```javascript
let score = 100; // 声明类语句，没 [[value]]
score = 99; // 表达式语句，会有 [[value]]
```

控制台的输入如下：

![](https://2598460105-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGNkDWo1TzHEOBRUxCRfy%2Fuploads%2Fl30qgwjXNfjrfNyBmDgv%2Fimage.png?alt=media\&token=9398d16a-2585-4f89-917e-b61044e3d78e)

表达式语句实际上就是一个表达式，它是由运算符连接变量或者直接量构成的。

1. Primary Expression，主要表达式。表达式的最小单位，它所涉及的语法结构也是优先级最高的
2. MemberExpression，成员表达式。
3. NewExpression，new 表达式。
4. CallExpression，函数调用表达式。
5. LeftHandSideExpression，左值表达式。
6. AssignmentExpression，赋值表达式。
7. Expression，表达式，用逗号运算符连接的赋值表达式。
8. 表达式的右边部分

## 2. 语句块

语句块就是用 `{}` 括起来的一组语句，可以嵌套。

语句块本身并不复杂，需要注意的是，当语句块的内部语句的 Completion Record 的 `[[type]]` 不为 normal 时，会打断语句块后续的语句执行。

比如，这是一个内部为普通语句的语句块：

{% code lineNumbers="true" %}

```javascript
// 注释里是每条语句执行后的 Completion Record
{
  var i = 1; // normal, empty, empty
  i ++; // normal, 1, empty
  console.log(i) //normal, undefined, empty
} // normal, undefined, empty
```

{% endcode %}

当我们在第三行加入一条 return 语句时，整个语句块就变成了非 normal 的，它会打断语句块中后续语句的执行，以此保证非 normal 的完成类型可以穿透复杂的语句嵌套结构，从而产生控制效果。

{% code lineNumbers="true" %}

```javascript
{
  var i = 1; // normal, empty, empty
  return i; // return, 1, empty
  i ++; 
  console.log(i)
} // return, 1, empty
```

{% endcode %}

## 3. 控制型语句

控制型语句会对不同类型的 Completion Record 产生反应。

1. 对内部产生影响的
   1. 条件语句：if, switch
   2. <mark style="color:red;">循环语句</mark>：for, while
      * for, for...in, for...of, for-await-of
      * while, do-while
   3. try
      * try 部分用于标识捕获异常的代码段
      * catch 部分则用于捕获异常后做一些处理
      * finally 则是用于执行后做一些必须执行的清理工作
2. 对外部产生影响的：continue, break, return, throw
   * 为了保证语义清晰，不建议用 throw 表达任何非异常逻辑

这两类控制型语句的配合，会产生控制代码执行顺序和执行逻辑的效果，这也是我们编程的主要工作。通常情况，我们熟悉的是 for/while + break/continue 和 try + throw 这样的组合。但是实际上，这两类是可以两两组合的。如下：

![](https://2598460105-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGNkDWo1TzHEOBRUxCRfy%2Fuploads%2FUF5bkCyePVE3pVSiiC2d%2F%E8%AF%AD%E5%8F%A5.png?alt=media\&token=602f084d-4f5a-4259-ac91-744f4683647a)

比如下面的两个示例：

{% code lineNumbers="true" %}

```javascript
function foo(){
  try{
    return 0; // return 语句生效了
  } catch(err) {
  } finally {
    console.log("a"); // finally 也会执行
  }
}

console.log(foo());
```

{% endcode %}

{% code lineNumbers="true" %}

```javascript
function foo(){
  try{
    return 0;
  } catch(err) {
  } finally {
    return 1; // 会覆盖 try 里的 return 值
  }
}

console.log(foo());
```

{% endcode %}

因为 finally 中的内容必须保证执行，所以 try/catch 执行完毕，即使得到的结果是非 normal 型的完成记录，也必须要执行 finally。而当 finally 执行也得到了非 normal 记录，则会使 finally 中的记录作为整个 try 结构的结果。

## 4. 带标签的语句

任何 JavaScript 语句都是可以加标签的，在语句前加冒号。

大部分时候，这个东西类似于注释，没有任何用处。唯一有作用的时候，是和 Completion Record（完成记录）类型中的 `[[target]]` 配合，用于跳出多层循环。比如：

{% code lineNumbers="true" %}

```javascript
outer: while(true) {
  inner: while(true) {
      break outer;
  }
}
console.log("finished")
```

{% endcode %}

break/continue 语句后，如果跟了关键字就会产生带 `[[target]]` 的完成记录。一旦完成记录带了 `[[target]]`，那么只有拥有对应 label 的循环语句会消费它。

## 5. 写在最后

JavaScript 中语句的执行过程，是由 Completion Record 类型来控制的。

因为语句之间存在着嵌套关系，所以语句的执行过程实际上主要是在一个树形结构上进行的。 树形结构的每一个节点在执行后都会产生一条 Completion Record，然后根据语句的结构和 Completion Record，JavaScript 实现了各种分支和跳出逻辑。

## 6. 参考

<https://time.geekbang.org/column/article/83860>
