日期:2014-05-16  浏览次数:20523 次

JavaScript语句后应该加分号么?
这是一个老生常谈的问题了。我之前就曾经写过一篇blog记录了我对此问题的实践与思考之旅。最近在知乎上又出现了这方面的争论,而且几乎是一面倒的支持“总是写分号”。这让我深深觉得是时候正本清源,祛除迷信了。于是我在问题http://www.zhihu.com/question/20298345下,花了整整一天时间写了以下的回答。

重新发在blog上,主要是因为此文过长,作为知乎的答案或许应该精简一下,但全文内容乃心血结晶,值得留存,照录如下。


首先,加还是不加,这是一个书写风格问题。而书写风格通常有一些外在的考量,比如团队所建立的规则或习惯。@玉伯  的答案就是基于此。我对此基本赞同,不过这其实有点避重就轻,呵呵。另外,即使团队有这样的规则,也未必要通过强制在写代码的时候就要这样写,而可以通过工具达成。比如在源码管理工具上挂上钩子,对提交的源代码自动整理格式。

其次,很多人提到代码压缩问题。我觉得这是非常扯淡的理由。如果2012年的今天一个JS压缩器还不能正确处理分号,这只能说明这个JS压缩器没有达到基本的质量要求,根本不值得信任。

@冯超 和 @CSS魔法 提到的jslint也是一个工具的反面例子。工具是帮助人的,而不应该是强迫人的。不明白这一点,你就不会理解为什么在已经有jslint很多年的情况下,还会出现jshint。

jshint对于不写分号会报warn,但可以通过asi选项关闭(在文件头加上/* jshint asi:true */即可)。

在asi选项说明里,jshint的文档是这样写的:
引用
There is a lot of FUD (fear, uncertainty and doubt) spread about semicolon spreaded by quite a few people in the community. The common myths are that semicolons are required all the time (they are not) and that they are unreliable. JavaScript has rules about semicolons which are followed by all browsers so it is up to you to decide whether you should or should not use semicolons in your code.


翻译如下(【】里是我添加的说明):

引用
关于分号有大量的FUD,且是由社区里的一小撮人【你知道是指谁】散布的。一个常见的流言是必须写分号,不写分号不可靠【流言的意思是不写分号会导致代码行为不确定】。实际上JS有明确的分号规则,并且所有浏览器【居然】都忠实遵守了规则。所以是否应该在你的代码里使用分号,完全可以由你自己决定【而不是由一小撮流言散布者或二逼工具强加于你】。


所以对于可不可以不加分号这个问题,社区是有结论的。

然后所谓“应该不应该”,就只是利弊分析,而不是非黑即白。其中也必定有一些如“可维护性”、“可理解性”甚至“代码美感”之类的貌似“贱人贱智”的问题。不过我相信有经验的程序员还是会在大多数问题上找到共识的。


这个世界上有许多语言。大量语言是不用分号作为EOS(End of Statement)的。有些偏执狂认为不用分号的语言都是垃圾,对此我没啥好说的。

有些语言也是可选分号,比如python。python是可以加分号作为语句结束的。当然绝大多数python程序员是不会加分号的(除了在一行里写多个语句)。所以python和js一样是可选分号!并且python的习惯是不写分号(仅在极少数情况下写)!

也有不少人会指摘python的语法太特殊,比如缩进啥的……不能算是c-style的。不过即使是C风格的语言,也有不写分号的,比如groovy。groovy和js一样是可选分号!并且groovy的习惯是不写分号(仅在极少数情况下写)!

所以至少从同样两个是可选分号的语言来看,不写分号在实践上是可行的。毕竟,既然被设计为可选,那么合理的推断是:语言的设计初衷是倾向于鼓励不写分号。

实际上,不少人(包括我)认为,c-style的分号本来就是多余的。为什么这么说?因为明确的EOS只是给编译器的提示而已。如果漏了分号,编译器会报错。既然它都报错了,显然它知道这里应该有EOS。既然它知道,那么干嘛还要我写?

给编译器以hint,这在几十年前是一个平衡编译器和用户成本的设计。某些语言(如Fortran、Basic等)选择用换行来作为EOS,这样每行只能一个语句,并且一个语句折行必须用特殊的接续符号。某些语言(如C)则选择了通过分号来达成,这样每行可以多个语句,并且一个语句也可以分布在多行。平心而论,我更喜欢前一种策略。不过现实是c-style的语法流传更广,至少当前的工业主流语言都是c-style的。

在c-style语言中,如果既要允许自由折行,又要避免额外的EOS(分号),编译器会较为复杂,光靠看token是不能确定语句是否结束的(即换行处有可能是语句结束,也有可能不是)——尽管在实践中只需要很少的规则,人就能一目了然的看清语句是否结束,但是parser要处理一切的极端情况,例如在换行前插入注释到底怎么算。而C的设计是遵循所谓worse is better的哲学,非常强调实现简单,一个明确的EOS对于编译器来说绝对是简单的。当初如果有人找K&R去要求应该由编译器判断这里该不该是语句结束,我打包票肯定被K&R扁死。有趣的是,lisp那一帮人更极端,如果你抱怨括号实在太密密麻麻的了,一定有人语重心长的告诉你S表达式才是王道。

其实像C++编译器也已经复杂到超乎想象,按理说可选分号真是小事一桩,但它因为要保持对C的完全兼容,所以还是必须写分号。

python和groovy的parser则都是有名的复杂。这并不完全由允许分号可选造成,但是可选的分号其实是整个语法设计哲学的一环。如Groovy的哲学是PHIM——Parse how I mean。

话说python的语法设计真的非常有意思。它也有问题,比如tab和空格混合,计算机之子@程劭非 曾经惊叹,居然有语言能通过改变注释(注释中可定义tabsize)就改变了语义和行为,真是极品。

当然后来者会吸取教训,比如coffeescript和jade之类的,也都是依赖缩进,但是都不允许tab和空格混用。

所以tab/sp这是python的坑。Guido Van Rossum现在就后悔了。从某种程度上说,JavaScript的分号就有点类似python的tab/sp问题。

正如混合tab/sp是出自GVR的良好初衷(让你们想用啥就用啥),可选分号也是出自BE的良好初衷(随便你写不写)。也如同tab/sp一样,良好的初衷并不代表就没有隐患。之所以python、groovy就没有可选分号的争议,而js就有争议,其实正说明js存在一些问题。

其实Groovy历史上也是有关于可选分号争议的,参见:http://blog.csdn.net/hax/article/details/139490。不幸的的是,与Groovy早期经过社区激烈的讨论才得到稳定语法不同,JS是一门早熟的语言,一些早期的设计失误没有机会被修复。自动分号插入算法就是其中之一。总体上,自动分号插入算法还算正常,但是在一些小地方留下了不易发觉的坑。比如return语句。


return
{
 a:1
}

在return后会自动插入分号,导致完全违背期望的结果。

这一古怪行为往往被解释为在JS中应采用一行内跟随大括号的书写风格(即Java的风格,或者说是K&R的C的原初风格,而不是C#风格),其实追根述源,问题还是出在分号上。

不要插分号的地方被插了分号,这挺坑爹了,但更更坑爹的是想要插的结果没插。这就