前言
分号对于编程语言来说不是必须的,比如对JavaScript来说,分号你可写可不写,而对于C#来说,没有分号无法通过编译。
那么分号的作用是什么?分号可以帮助编译器或解释器正确地解析代码。如果没有分号,编译器可能会把多个语句视为一个语句,导致语法错误或运行错误。
那么,今天我们聊一下在JavaScript中,我们不写分号,会发生什么。
ASI(自动分号)
在JavaScript
中有一个自动分号机制,意思是JS解释器会自动为代码行补上缺失的分号,即自动分号插入(Automatic Semicolon
Insertion,ASI)。 因为如果缺失了必要的 ;,代码将无法运行,语言的容错性也会降低。ASI
能让我们忽略那些不必要的 ;。
但是,ASI
只在换行符处起作用,而不会在代码行的中间插入分号。并且只有代码行末尾与换行符之间除了空格和注释之外没有比的内容时,它才会这样做。
例如:
代码语言:javascript复制var a = 42,b
c;
如果b
和c
之间出现a
,的话(即使另起一行),c
会被作为var
语句的一部分来处理。在上例中,JavaScript
判断b
之后应该有 ;,所以c
; 被处理为一个独立的表达式语句。 又比如:
var a = 42, b = "foo";
a
b // foo
上述代码同样合法,不会产生错误,因为 ASI 也适用于表达式语句。
ASI 在某些情况下很有用,比如:
代码语言:javascript复制var a = 42;
do {
// ...
} while (a) // 这里应该有;
a;
语法规定do..while
循环后面必须带 ;,而while
和for
循环后则不需要。大多数开发人员都不记得这一点,此时ASI
就会自动补上分号。
var a = 42;
while (a) {
// ...
} // 这里可以没有;
a;
其他涉及ASI
的情况是 break、continue、return 和 yield(ES6)等关键字:
function foo(a) {
if (!a) return
a *= 2;// ..
}
由于ASI
会在return
后面自动加上;,所以这里return
语句并不包括第二行的a *= 2。 return 语句的跨度可以是多行,但是其后必须有换行符以外的代码:
function foo(a) {
return (
a * 2 3 / 12
)
}
上述规则对 break、continue 和 yield 也同样适用。
纠错机制
是否应该完全依赖ASI
来编码,这是JavaScript
社区中最具争议性的话题之一(除此之外 还有 Tab 和空格之争)。
大多数情况下,分号并非必不可少,不过 for( .. ) .. 循环头部的两个分号是必需的。
正方认为ASI
机制大有裨益,能省略掉那些不必要的 ;,让代码更简洁。此外,ASI
让许
多 ; 变得可有可无,因此只要代码没问题,有没有 ; 都一样。
反方则认为ASI
机制问题太多,对于缺乏经验的初学者尤其如此,因为自动插入 ; 会无意中改变代码的逻辑。还有一些开发人员认为省略分号本身就是错误的,应该通过linter
这样的工具来找出这些错误,而不是依赖JavaScript
引擎来改正错误。
仔细阅读规范就会发现,ASI
实际上是一个“纠错”(error correction)机制。这里的错误是指解析器错误。换句话说,ASI
的目的在于提高解析器的容错性。
究竟哪些情况需要容错呢?我认为,解析器报错就意味着代码有问题。对ASI
来说,解析器报错的唯一原因就是代码中缺失了必要的分号。
我认为在代码中省略那些“不必要的分号”就意味着“这些代码解析器无法解析,但是仍然可以运行”。
仅仅为了追求“代码的美观”,省去一些键盘输入,这样做不免有点得不偿失。
这与空格和Tab
之争还不是一回事,后者仅涉及代码的美观问题,前者则关系到原则问题:是遵循语法规则来编码,还是打规则的擦边球。
换个角度来看,依赖于 ASI 实际上是将换行符当作有意义的“空格”来对待。在一些语言 (如 Python)中空格是有意义的,但这对JavaScript
是否适用呢?
我建议在所有需要的地方加上分号,将对ASI
的依赖降到最低。
以上观点并非一家之言。JavaScript
的作者 Brendan Eich 早在 2012 年就说过这样的话
(http://brendaneich.com/2012/04/the-infernal-semicolon/):
ASI 是一个语法纠错机制。若将换行符当作有意义的字符来对待,就会遇到很多 问题。多希望在 1995 年 5 月的那十天里(ECMAScript 规范制定期间),我让换行符承载了更多的意义。但切勿认为 ASI 真的会将换行符当作有意义的字符。