Skip to content

JavaScript使用生成器时不写分号出现的问题

问题

下面是一段斐波那契数列的生成器代码

javascript
function* fibonacciSequence() {
  let x = 0, y = 1;
  for(;;) {
    yield y;
    [x, y] = [y, x + y];
  }
}

由于我平时写js的习惯是不写分号,所以我在敲上面那段代码时去掉了分号

javascript
function* fibonacciSequence() {
  let x = 0, y = 1
  for(;;) {
    yield y
    [x, y] = [y, x + y]
  }
}

此时我想要知道第20个斐波那契数,于是写下这段代码

javascript
function fibonacci(n) {
  for(let f of fibonacciSequence()) {
    if(n-- <= 0) {
      return f
    }
  }
}

此时调用方法发现结果是

javascript
fibonacci(20) // => [ 1, 1 ] 与预想的结果不符合

而正常的第20个斐波那契数应该是10946
要知道JavaScript解释器是有ASI(Automatic Semicolon Insertion)机制的,即自动插入分号,那两段代码只有分号的区别,初步判断是ASI没有照着期望的方向添加分号
于是通过Esprima Parser将js的语法树解析出来看看之后发现
不加分号的yield语句后面跟的是赋值表达式
image.png
而加分号之后的yield语句后面是标识符
image.png

ASI规则

ECMAScript 标准定义的 ASI 包括三条规定两条例外
三条规则

  1. 解析器从左往右解析代码(读入 token),当碰到一个不能构成合法语句的 token 时,他会在以下几种情况中,在该 token 之前插入分号,此时不合群的 token 被称为违规 tokenoffending token
    • 1.1 如果这个 token 跟上一个 token 之间有至少一个换行。
    • 1.2 如果这个 token}
    • 1.3 如果前一个 token) 它会试图把签名的 token 理解成 do-while 语句并插入分号。
  2. 当解析到文件末尾发现语法还是无法构成合法的语句,就会在文件末尾插入分号。
  3. 当解析碰到 restricted production 的语法(比如 return),并且在 restricted production规定的 [no LineTerminator here] 的地方发现换行,那么换行的地方就会被插入分号。

两条例外

  1. 分号不能被解析成空语句。
  2. 分号不能被解析成 for 语句的两个分号之一。

分析

根据ASI规则可知JavaScript解释器解析到yield语句时
第一个token是“y”第二个token是“[”不满足添加分号的条件
image.png
于是上面不加分号的代码就会被解析为

javascript
function* fibonacciSequence() {
  let x = 0, y = 1;
  for(;;) {
    yield y[x, y] = [y, x + y];
  }
}

所以和预想的不符合


2022/12/12

Author @Henry Ge