第五章:文法 - 自动分号
自动分号
当JavaScript认为在你的JS程序中特定的地方有一个;
时,就算你没在那里放一个;
,它就会进行ASI(Automatic Semicolon Insertion —— 自动分号插入)。
为什么它这么做?因为就算你只省略了一个必需的;
,你的程序就会失败。不是非常宽容。ASI允许JS容忍那些通常被认为是不需要;
的特定地方省略;
。
必须注意的是,ASI将仅在换行存在时起作用。分号不会被插入一行的中间。
基本上,如果JS解析器在解析一行时发生了解析错误(缺少一个应有的;
),而且它可以合理的插入一个;
,它就会这么做。什么样的地方对插入是合理的?仅在一个语句和这一行的换行之间除了空格和/或注释没有别的东西时。
考虑如下代码:
var a = 42, b
c;
JS应当将下一行的c
作为var
语句的一部分看待吗?如果在b
和c
之间的任意一个地方出现一个,
,它当然会的。但是因为没有,所以JS认为在b
后面有一个隐含的;
(在换行处)。如此c;
就剩下来作为一个独立的表达式语句。
类似地:
var a = 42, b = "foo";
a
b // "foo"
这仍然是一个没有错误的合法程序,因为表达式语句也接受ASI。
有一些特定的地方ASI很有帮助,例如:
var a = 42;
do {
// ..
} while (a) // <-- 这里需要;!
a;
文法要求do..while
循环后面要有一个;
,但是while
或for
循环后面则没有。但是大多数开发者都不记得它!所以ASI帮助性地介入并插入一个。
如我们在本章早先说过的,语句块儿不需要;
终结,所以ASI是不必要的:
var a = 42;
while (a) {
// ..
} // <-- 这里不需要;
a;
另一个ASI介入的主要情况是,与break
,continue
,return
,和(ES6)yield
关键字:
function foo(a) {
if (!a) return
a *= 2;
// ..
}
这个return
语句的作用不会超过换行到a *= 2
表达式,因为ASI认为;
终结了return
语句。当然,return
语句 可以 很容易地跨越多行,只要return
后面不是除了换行外什么都没有就行。
function foo(a) {
return (
a * 2 + 3 / 12
);
}
同样的道理也适用于break
,continue
,和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有意义的换行,那么要小心不要使用它。