`
hax
  • 浏览: 951941 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

JavaScript语句后应该加分号么?

    博客分类:
  • JS
阅读更多
这是一个老生常谈的问题了。我之前就曾经写过一篇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#风格),其实追根述源,问题还是出在分号上。

不要插分号的地方被插了分号,这挺坑爹了,但更更坑爹的是想要插的结果没插。这就是括号的问题。如果下一行的开始是“(”、“[”上一行的结尾不会被加上“;”。

如:

a = b
(function(){
  ...
})()


会被解释为
a = b(function(){...})()



其实如果我们真想表达上述代码,通常会这样写:
a = b(function(){
  ...
})()

再如:
a = b
[1,2,3].forEach(function(e){
 console.log(e)
})


实际效果等价于
a = b[3].forEach(function(e){
  console.log(e)
})


坑爹的是,搞不好这代码说不定还能运行!你要事后通过调试发现这些错误是相当滴痛苦啊。

当然这也不能全赖BE。在JS的早期,还没有数组迭代方法 Array.prototype.forEach/map/filter...等,也没有今天常见的 (function(){...})() 惯用法,所以这个问题其实很不明显。但是到了今天,这些坑爹的问题就都冒出来了。

实际上,“+”、“-”、“/”也有问题,但是我们几乎不会在实践中遇到。因为你几乎不可能会写出行首以“+”、“-”、“/”开始的语句,除了 ++i 之类的语句(但是其实我们都会写成 i++)。

不过这些问题的解决方案其实也很简单。只要在“[”、“(”、“+”、“-”、“/”等之前加分号就可以了:

a = b
;(function(){
  ...
})()

a = b
;[1,2,3].forEach(function(e){
  console.log(e)
})



有些同学觉得这样很丑。没问题,你可以用 void 替代“;”。

也有不少人觉得这是一种“不一致”,需要记住额外的法则。

我承认采取这样一种方法你必须记住一些特例。但是几乎所有的语言都有一些历史原因导致的坑,并且JS也不止这一个坑。更关键的是,即使你采用了总是写“;”的方法,仍然不能避免掉进EOS的坑,因为造成问题的asi特性仍然存在。比如之前提到的return后面会自动插分号的问题。

“总是写分号”,相比“不写分号但是edge case要在行首加分号”,看上去要更“简单”,但这只是描述简单,实际做起来未必更简单。

比如你必须要记得,function表达式后面也要写“;”!

如:

function a() {
 ...
}
[1,2,3].forEach(...)


这代码是没问题的,但是你改成

var a = function () {
  ...
}
[1,2,3].forEach(...)


就有问题了!这坑爹!

对于“始终加分号派”来说,结果就会变成函数后面也一定要加分号。(你分得清函数声明和函数表达式吗?坑爹啊,不如都加!)但是为什么函数就加而 if ... {} 或 for (...) {...} 结构里的大括号后面就不加分号呢?这不是也不一致嘛。

而且,同样是一条特殊规则,行首加分号的规则比函数表达式后面加分号的规则其实要简单


var a = function () {
  ...
}
[1,2,3].forEach(...)


还是以上面代码为例。

行首是否要加分号,我只要看本行的第一个字符就可以了。因为对于object[prop]这样的意图,其实没有程序员会写出

object
[prop]

这样的代码。如果他要折行,一定是写成
object[
 prop
]

所以行首第一个字符如果是括号,毋庸置疑的,这一定是一个新语句的开始。

反过来,你如果要判断“}”后面是否要加“;”,你得向上回溯,看清楚整段代码是一个结构呢?还是一个函数?如果是函数的话,是函数声明呢?还是函数表达式!

许多时候,你可能向上翻几页还没找到对应的“{”!或者已经忘记了是几层缩进了!

由此可见,对于人来说,行首特例加分号的策略其实更简单易行。而总是加分号的策略听上去简单,执行起来却难!除非你的策略最后变成了所有“}”之后都加分号——我真见过有人这么做的。


对人是这样,下面再来看看对机器(引入工具)的情形。特别的,因为有不少人表示他遵循总是写分号的方式是因为他严重依赖jslint。所以我就拿jslint开刀。

对于总是加分号的策略,你希望工具能提示你哪里缺少分号。但是实际情况是,你必须尽量避免写出有歧义的跨行语句,因为工具很难判断是有意为之,还是忘记写“;”。

比如:

a = b
(function(){
...
})();

这代码在jslint的提示是:Expected '(' at column 5, not column 1.

请问你是应该真的按照它的提示把括号移动到b后面吗??

仔细考虑一下,你就知道这个问题不好回答。因为jslint给出的建议其实是基于“这是合法的代码,只是格式不妥”。虽然我们都知道这更可能是忘记写分号。

再来一个更坑爹的例子:


/*jslint white: true */
var a,b,c,d,e,f,g,h,i,j,k,l,m,o,s;
a=b+c*d-e
   /f/g-h*i/j
/f/g.exec(s).map(f);


这段代码在jslint里是不报错的!!!

但是我们是可以看出来这代码很有可能是缺少分号。

这里可以看出,如果排除了whitespace的格式提示(这事儿还是挺常见的,毕竟许多人不喜欢被强制加那么多空格规则),jslint其实无法在我们最需要帮助的时候帮到我们!因为它无法判断这个地方到底是有意为之(不用“;”而跨行),还是忘记写“;”。

反过来说,如果采取行首特例加“;”的习惯,其实工具是很容易判断你是否忘记加了分号。如果加上一些对缩进信息的判断来排除极少数不良的折行习惯(出warning即可),工具甚至能自动把所有这类分号都加上。


两种策略:

1. 我总是写分号,让工具告诉我哪里我忘记写了(但是有时候可能还报不出来,或报了个其他信息)

2. 我总是不写分号,让工具自动把(由于语言设计缺陷所要求的)必须的分号加上去

哪种更好?


总结:

我所推荐的不写分号的方式,其实不仅是不写分号,而是同时采用更严格的跨行策略,即只允许在当前行处于未完成状态时跨行(就像你在jsshell中输入代码一样)。这条规则其实并不需要特别强制,因为绝大多数程序员一直就是这样在执行。诚然,存在少数人习惯写这样有歧义的折行代码:


a = b + c
     + d + e
     + f  + g


但是这个习惯不难纠正,并且工具根据缩进等信息是完全能检测到的。


说到这里,也许有些同志认为这只能说明jslint太挫,不能证明到处写“;”的风格不好。因为工具也可以同时加上其他限制嘛。不过你仔细想想,可以发现这是一个悖论。如果jslint够智能,引入了其他与分号无关的代码风格要求,比如空格和缩进,还有折行风格,确实也可以更精确的找到所有漏掉分号的地方。但是那无非再次证明了一点:编译器(代码分析器)完全可以知道哪里应该有EOS。既然所有的分号其实可以由机器自行加上(无论是加在行首还是行尾),那么我们自己还要手写所有分号的意义到底在哪里?!

以上。








4
6
分享到:
评论
14 楼 u012814086 2015-05-27  
下意识想到了Golang
13 楼 justjavac 2013-08-15  
wzwahl36 写道
为什么会产生分号争议?python为什么不存在分号争议?考虑过这个问题没?
机制不一样,造成了结果不一样,好好加分号,会把你累死?会产生争议?不加分号才是造成这个争议的最大原因。

javascript之父就倾向于不是用分号。
12 楼 wzwahl36 2013-08-15  
为什么会产生分号争议?python为什么不存在分号争议?考虑过这个问题没?
机制不一样,造成了结果不一样,好好加分号,会把你累死?会产生争议?不加分号才是造成这个争议的最大原因。
11 楼 ih0qtq 2012-07-13  
不看文章,直接看评论,也是一种态度.
10 楼 justjavac 2012-06-20  
hax 写道
本文并非转载。OK?

justjavac 写道
一个好的程序员不应该转载了不注明出处。



先向hax道歉,原来 @贺师俊 哥再次啊。领教了。
9 楼 xiaotot 2012-06-20  
必须写,不必须写,有这么绝对么?
8 楼 liu78778 2012-06-20  
justjavac 写道
应注明出处。『知乎』

Hax 真名就是贺师俊,想发到哪是人家的自由.

你们这帮家伙:
1.听风就是雨,无视明显的事实(什么都没搞清就下评论)
2.缺乏程序员最基础的品质"对事物本质的好奇心".
3.缺乏对人的起码尊重.

hax不用理会他们
7 楼 hax 2012-06-19  
本文并非转载。OK?

justjavac 写道
一个好的程序员不应该转载了不注明出处。

6 楼 justjavac 2012-06-19  
ih0qtq 写道
liu78778 写道
a34020249 写道
真的懒得讲你的,一个分号能说这么一大堆,渣。。。。。

看你那变成风格渣。。。。。。。

一个好的程序员要有打破沙锅问到底的精神, 另外人家费力分析写出来的, 你不需要可以不看, 但是不能否认别人也不需要



这是一种态度,如果是程序员,连这种态度都没有的话还做什么程序员.

一个好的程序员不应该转载了不注明出处。
5 楼 tlde_ti 2012-06-19  
这一古怪行为往往被解释为在JS中应采用一行内跟随大括号的书写风格(即Java的风格,或者说是K&R的C的原初风格,而不是C#风格),其实追根述源,问题还是出在分号上。
-------------------------
这一点有点不赞同,这个的确是semi-colon inference导致没有实现这种风格,但那也是因为支持这种风格以后,inference要处理的情况无谓的变得很复杂,而其达到的效果和
return {
  a:1
}
这种风格确是一样的。没有多少好处却可能带来更多的工作和bug.
所以我是觉的不支持这种风格不算问题.
4 楼 ih0qtq 2012-06-19  
liu78778 写道
a34020249 写道
真的懒得讲你的,一个分号能说这么一大堆,渣。。。。。

看你那变成风格渣。。。。。。。

一个好的程序员要有打破沙锅问到底的精神, 另外人家费力分析写出来的, 你不需要可以不看, 但是不能否认别人也不需要



这是一种态度,如果是程序员,连这种态度都没有的话还做什么程序员.
3 楼 liu78778 2012-06-19  
a34020249 写道
真的懒得讲你的,一个分号能说这么一大堆,渣。。。。。

看你那变成风格渣。。。。。。。

一个好的程序员要有打破沙锅问到底的精神, 另外人家费力分析写出来的, 你不需要可以不看, 但是不能否认别人也不需要
2 楼 a34020249 2012-06-19  
真的懒得讲你的,一个分号能说这么一大堆,渣。。。。。

看你那变成风格渣。。。。。。。
1 楼 justjavac 2012-06-19  
应注明出处。『知乎』

相关推荐

    浅谈javascript的分号的使用

    js语句后应该加分号吗? javascript大括号后面应使用分号吗?JS中function 的开头有加感叹号、分号是什么意思呢? Js多个文件集成成一个文件后,压缩代码时避免发生语法错误,可以如下处理 一、js 前加分号 例如:;...

    【JavaScript源代码】JavaScript中分号的一些细节.docx

     JavaScript 中的分号是可选的,加不加分号主要是个代码风格问题。一种风格是使用分号明确结束语句,即便这些分号不是必需的;另一种风格是尽可能的不加分号,只在必要的情况才加。我个人就不喜欢加分号,当然喜欢...

    javascript 分号总结及详细介绍

    javascript每条语句都是以分号结束,但由于javascript具有分号自动插入规则,所有不同的编程人员有不同的习惯,有的加分号,有的不加分号,那么到底加分号好还是不加分号好?本文章向大家探讨javascript每条语句该不...

    关于JavaScript语句后面的分号问题

    下面通过本文给大家详细介绍javascript中的语句后面的分号问题,本文给大家介绍的非常详细,需要的朋友参考下吧

    如何防止JavaScript自动插入分号

    JavaScript语言有一个机制:在解析时,能够在一句话后面自动插入一个分号,用来修改语句末尾遗漏的分号分隔符。然而,由于这个自动插入的分号与JavaScript语言的另一个机制发生了冲突,即所有空格符都被忽略,因此...

    JavaScript 语句

    JavaScript 语句 JavaScript 语句向浏览器发出的命令。语句的作用是告诉浏览器该做什么。...分号用于分隔 JavaScript 语句。 通常我们在每条可执行的语句结尾添加分号。 使用分号的另一用处是在一行中编写多条语

    JavaScript语法

    JavaScript的基本语法使用技巧 avaScript语法 JavaScript脚本程序的格式 JavaScript语言区分大小写。 JavaScript脚本程序须... ... JavaScript语句末尾可以加分号,此时多条语句可以写在一行,语句中间以分号隔开。

    JavaScript权威指南-第六版

     5.8 JavaScript语句小结116  第6章 对象118  6.1 创建对象120  6.2 属性的查询和设置123  6.3 删除属性127  6.4 检测属性128  6.5 枚举属性130  6.6 属性getter和setter132  6.7 属性的特性134

    Web前端开发技术-JavaScript代码编写.pptx

    JavaScript的开发 JavaScript代码编写 JavaScript Web前端开发技术 JavaScript的开发 2.JavaScript代码编写 JavaScript代码的3种书写位置: ...如果一条语句结束后,换行书写下一条语句,后面的分号

    JavaScript语言参考手册

    多个语句可以出现在一行上,但各自中间应该用分号隔开。 break comment continue delete do...while export for for...in function if...else import labeled return switch var while with 第四章 核心 这一章...

    JavaScript权威指南(第6版)中文版pdf+源代码

     5.8 JavaScript语句小结116  第6章 对象118  6.1 创建对象120  6.2 属性的查询和设置123  6.3 删除属性127  6.4 检测属性128  6.5 枚举属性130  6.6 属性getter和setter132  6.7 属性的特性134  6.8 对象...

    JavaScript权威指南(第6版)

    5.8 JavaScript语句小结 第6章 对象 6.1 创建对象 6.2 属性的查询和设置 6.3 删除属性 6.4 检测属性 6.5 枚举属性 6.6 属性getter和setter 6.7 属性的特性 6.8 对象的三个属性 6.9 序列化对象 6.10 对象方法 第7章 ...

    JavaScript权威指南(第6版)(附源码)

    5.8 JavaScript语句小结 第6章 对象 6.1 创建对象 6.2 属性的查询和设置 6.3 删除属性 6.4 检测属性 6.5 枚举属性 6.6 属性getter和setter 6.7 属性的特性 6.8 对象的三个属性 6.9 序列化对象 6.10 对象方法 第7章...

    Javascript核心读书有感之语句

    正如英文语句以句号结尾,javascript以分号结尾。 表达式计算出一个值,但语句使某件事发生。 “使某件事发生”的一个方法是计算带有副作用的表达式。诸如赋值和函数调用这些有副作用的表达式,是可以作为单独的语句...

    JavaScript 权威指南(第四版).pdf

     5.8 JavaScript语句小结116  第6章 对象118  6.1 创建对象120  6.2 属性的查询和设置123  6.3 删除属性127  6.4 检测属性128  6.5 枚举属性130  6.6 属性getter和setter132  6.7 属性的特性134  6.8 对象...

    浅析Javascript的自动分号插入(ASI)机制

    前言 相信从事过C#和Java的大家都知道分号是用作断句(EOS,end of statement)的,而且...1、如果插入分号后解析结果是空语句,那么不会自动插入分号。 例子:(空语句,else 前不加分好) if (a > b) else c = d 2

    JavaScript权威指南(第6版)(中文版)

    5.8 JavaScript语句小结 第6章 对象 6.1 创建对象 6.2 属性的查询和设置 6.3 删除属性 6.4 检测属性 6.5 枚举属性 6.6 属性getter和setter 6.7 属性的特性 6.8 对象的三个属性 6.9 序列化对象 6.10 对象方法 第7章 ...

    JavaScript权威指南(第6版)中文文字版

    5.8 javascript语句小结 116 第6章 对象 118 6.1 创建对象 120 6.2 属性的查询和设置 123 6.3 删除属性 127 6.4 检测属性 128 6.5 枚举属性 130 6.6 属性getter和setter 132 6.7 属性的特性 134 6.8 对象的三个属性 ...

Global site tag (gtag.js) - Google Analytics