自动分号

当JavaScript认为在你的JS程序中特定的地方有一个;时,就算你没在那里放一个;,它就会进行ASI(Automatic Semicolon Insertion —— 自动分号插入)。

为什么它这么做?因为就算你只省略了一个必需的;,你的程序就会失败。不是非常宽容。ASI允许JS容忍那些通常被认为是不需要;的特定地方省略;

必须注意的是,ASI将仅在换行存在时起作用。分号不会被插入一行的中间。

基本上,如果JS解析器在解析一行时发生了解析错误(缺少一个应有的;),而且它可以合理的插入一个;,它就会这么做。什么样的地方对插入是合理的?仅在一个语句和这一行的换行之间除了空格和/或注释没有别的东西时。

考虑如下代码:

  1. var a = 42, b
  2. c;

JS应当将下一行的c作为var语句的一部分看待吗?如果在bc之间的任意一个地方出现一个,,它当然会的。但是因为没有,所以JS认为在b后面有一个隐含的;(在换行处)。如此c;就剩下来作为一个独立的表达式语句。

类似地:

  1. var a = 42, b = "foo";
  2. a
  3. b // "foo"

这仍然是一个没有错误的合法程序,因为表达式语句也接受ASI。

有一些特定的地方ASI很有帮助,例如:

  1. var a = 42;
  2. do {
  3. // ..
  4. } while (a) // <-- 这里需要;!
  5. a;

文法要求do..while循环后面要有一个;,但是whilefor循环后面则没有。但是大多数开发者都不记得它!所以ASI帮助性地介入并插入一个。

如我们在本章早先说过的,语句块儿不需要;终结,所以ASI是不必要的:

  1. var a = 42;
  2. while (a) {
  3. // ..
  4. } // <-- 这里不需要;
  5. a;

另一个ASI介入的主要情况是,与breakcontinuereturn,和(ES6)yield关键字:

  1. function foo(a) {
  2. if (!a) return
  3. a *= 2;
  4. // ..
  5. }

这个return语句的作用不会超过换行到a *= 2表达式,因为ASI认为;终结了return语句。当然,return语句 可以 很容易地跨越多行,只要return后面不是除了换行外什么都没有就行。

  1. function foo(a) {
  2. return (
  3. a * 2 + 3 / 12
  4. );
  5. }

同样的道理也适用于breakcontinue,和yield

纠错

在JS社区中斗得最火热的 宗教战争 之一(除了制表与空格以外),就是是否应当严重/唯一地依赖ASI。

大多数,但不是全部的,分号是可选的,但是for ( .. ) ..循环的头部的两个;是必须的。

在这场争论的正方,许多开发者相信ASI是一种有用的机制,允许他们通过省略除了必须(很少几个)以外的所有;写出更简洁(和更“美观”)的代码。他们经常断言因为ASI使许多;成为可选的,所以一个 不带它们 而正确编写的程序,与 带着它们 而正确编写的程序没有区别。

在这场争论的反方,许多开发者将断言有 太多 的地方可以成为意想不到的坑了,特别是对那些新来的,缺乏经验的开发者来说,无意间被魔法般插入的;改变了程序的含义。类似地,一些开发者将会争论如果他们省略了一个分号,这就是一个直白的错误,而且他们希望他们的工具(linter等等)在JS引擎背地里 纠正 它之前就抓住他。

让我分享一下我的观点。仔细阅读语言规范,会发现它暗示ASI是一个 纠错 过程。你可能会问,什么样的错误?明确地讲,是一个 解析器错误。换句话说,为了使解析器失败的少一些,ASI让它更宽容。

但是宽容什么?在我看来,一个 解析器错误 发生的唯一方式是,它被给予了一个不正确/错误的程序去解析。所以虽然ASI在严格地纠正解析器错误,但是它得到这样的错误的唯一方式是,程序首先就写错了 —— 在文法要求使用分号的地方忽略了它们。

所以,更直率地讲,当我听到有人声称他们想要省略“可选的分号”时,我的大脑就将它翻译为“我想尽量编写最能破坏解析器但依然可以工作的程序。”

我发现这种立场很荒唐,而且省几下键盘敲击和更“美观的代码”的观点是软弱无力的。

进一步讲,我不同意这和空格与制表符的争论是同一种东西 —— 那纯粹是表面上的 —— 我宁愿相信这是一个根本问题:是编写遵循文法要求的代码,还是编写依赖于文法异常但仅仅将之忽略不计的代码。

另一种看待这个问题的方式是,依赖ASI实质上将换行视为有意义的“空格”。像Python那样的其他语言中有真正的有意义的空格。但是就今天的JavaScript来说,认为它拥有有意义的换行真的合适吗?

我的意见是:在你知道分号是“必需的”地方使用分号,并且把你对ASI的臆测限制到最小。

不要光听我的一面之词。回到2012年,JavaScript的创造者Brendan Eich说过下面的话(http://brendaneich.com/2012/04/the-infernal-semicolon/):

这个故事的精神是:ASI是一种(正式地说)语法错误纠正过程。如果你在好像有一种普遍的有意义的换行的规则的前提下开始编码,你将会陷入麻烦。
..
如果回到1995年五月的那十天,我希望我使换行在JS中更有意义。
..
如果ASI好像给了JS有意义的换行,那么要小心不要使用它。