最近彻底从分号党变节为无分号群众,分号党的日子对 Javascript 分号的原理不求甚解,这几天仔细了解了下,这篇文章总结下 ASI(automatic semicolon insertion, 分号自动插入机制)

不能省略的分号

我在初学 Javascript 时,也搜过一些代码是否要加分号的资料,很多文章提到 「Javascript 权威指南」中的一段话

如果一条语句以[/+、或-开始,那么它极有可能和前一条语句合在一起解释。

并奉若圭臬,仅是这几个字符的相关例子就占据了文章的大半篇幅。

看完这些文章,我对于 Javascript 分号的认知时,除了这几个字符前面需要特殊加分号外,其余的分号均可以省略,而日常开发中,以这几个字符开始的语句很少遇到,最常见的只有以 ( 开始的立即执行函数(IIFE),随着时间的推移,我只记得在立即执行函数前需要特殊加分号。

这种不求甚解的思维让我在技术学习的道路上始终踟蹰不前,其实稍微考虑深一点,就应该意识到,/+- 都在,那 * 呢?花点时间理解 IIFE 的原理就应该知道,只需要把函数声明转换成表达式直接执行就可以,除了 () 还可以用 !+-……

关于 ASI

虽然被称为分号自动插入,但在代码实际的解析中,并没有分号插入,这个机制用于判断输入流(input stream)的终止。下文中,我们以插入分号代称这种终止。

ASI 机制

ECMAScript 标准定义了三条规则和两种例外情况:

from §11.9.1 Rules of Automatic Semicolon Insertion

规则

1.Javascript 代码在从左往右被解析的过程中,当碰到一个不能构成合法语句的 token 时,下面几个条件至少一条成立,解析器就在在这个 token 前插入分号:

  • 如果这个 token 与上一个 token 之间至少有一个换行
  • 如果这个 token 是 }
  • 如果上一个 token 是 ),那么解析器会尝试将前面的 token 解析为 do...while 语句,并插入分号
    2.如果已经读到输入流的结尾了,解析器仍旧判定语法不合法,那么会在输入流的结尾插入分号
    3.如果解析过程中遇到 restricted production,当在 restricted production 语法中禁止换行的位置([no LineTerminator here])出现换行时,解析器会在换行的位置插入分号

例外

以下情况,将不会被插入分号:

  • 插入的分号被解析为空语句
  • for 循环中

restricted production

Update 表达式:

  • LeftHandSideExpression [no LineTerminator here] ++
  • LeftHandSideExpression [no LineTerminator here] --

Continue 语句:

  • continue;
  • continue [no LineTerminator here] LabelIdentifier;

Break 语句:

  • break;
  • break [no LineTerminator here] LabelIdentifier;

Return 语句:

  • return;
  • return [no LineTerminator here] LabelIdentifier;

Throw 语句:

  • throw;
  • throw [no LineTerminator here] LabelIdentifier;

箭头函数:

  • Parameter [no LineTerminator here] => 函数体;

Yield 表达式:

  • Yield [no LineTerminator here] * AssignmentExpression;
  • Yield [no LineTerminator here] AssignmentExpression;

例子

1
2
3
4
5
{ 1
2 } 3
// 依据规则一和二,解析结果
{ 1
;2 ;} 3;
1
2
3
4
5
return
a + b
// 依据规则三,解析结果
return;
a + b;
1
2
3
if(a > b)
else c = d
// 如果在 else 前插入分号,这个分号将被解析为
1
2
3
for(a; b
)
// 依据例外情况二,不会插入分号,代码报错

开发应用

现在我已经完全适应了没有分号的开发,之前存在的不确定因素,只需花点时间制定一个可以长期沿用的规范就能解决,例如好的开发习惯可以让我们避免使用 [].forEach() 之类的写法,同时立即执行函数也有多种写法让我们避免 (function(){})() 这种情况的出现。

参考: