现状
JavaScript 分号之争由来已久,目前最被认可的结论是:JavaScript 语句末尾是否加分号是一个编程风格问题,可以加,也可以不加。
选择
事情总要有个决定的!
——卫斯理《只限老友》(卫斯理系列最后一句话)
那么,到底加不加分号呢?
既然是一个风格问题,我们要明白的是如何选择。选择之前,我们来看看做出选择的前提是什么:
加分号
:前提是你了解如何以加分号的方式写 JavaScript。不加分号
:前提是你了解如何以不加分号的方式写 JavaScript。
初期,了解哪种,选择哪种。如果都不了解,其实你只是胡乱写 JavaScript。若都有了解,自然可以根据喜好,任性!不过我们往往需要考虑更多,如今这个你也造轮子,我也改框架的时代,其实我们需要同时了解这两种规则,这是语言设计带来的问题(JavaScript 有自动插入分号的机制,然而并不可靠),避无可避。
不加分号
选择不加分号的风格,就要了解哪些情况下不加分号会产生歧义,然后避免。
最著名的例子:
1 | return |
return 后面会自动加入上分号,这并不是期望的结果。所以,要写成:
1 | return { |
上面是一个不想加而被加的例子,下面看一个想被加而没有加的例子:
1 | var buttons = document.querySelectorAll("button") |
这段代码会报错,因为它被解释为这样:
1 | var buttons = document.querySelectorAll("button")[].forEach.call(buttons, function(elem) { |
“[“, “(“, “+”, “-“, “/“ 这些符号也有同样的问题。所以,不加分号的风格,要避免这些情况。于是,可以这样:
1 | var buttons = document.querySelectorAll("button") |
也就是在 “[“, “(“, “+”, “-“, “/“ 这些前面加分号,如果感觉分号不好看,还可以这样:
1 | var buttons = document.querySelectorAll("button") |
最好的方式也许是这样:
1 | var buttons = document.querySelectorAll("button") |
然而,还没有结束,Zepto, Sea.js, Vue.js
都是不加分号阵营,观察它们的源代码,你会发现一个问题。这些代码中极少用 var 定义函数。这是因为:
1 | var add = function(a, b) { |
会被解释为:
1 | var add = function(a, b) { |
如果代码是这样:
1 | var mark = [[], [], [1, 2]] |
还会不报错,对排错很不友好。
加分号
以加分号的方式写代码,不需要特别的规则,只需要熟练掌握 JavaScript 语法。加分号的风格,只有在漏加分号的情况下才会出问题:
1 | var add = function(a, b) { |
这是前面用到过的例子,代码会解释为 }[] 的结构。加分号阵营有:jQuery, Underscore.js, Backbone.js…
推荐
我推荐的是加分号风格,因为加分号不需要多余的附加规则。当然,你也可以选择不加分号风格,只要你清楚由此带来的附加限制。
争议
贺师俊写过一篇《JavaScript语句后应该加分号么?》。他推荐不加分号的风格,文章中谈了团队习惯、编程语言的风格、代码构建等更深入的问题,但是他的每个推论我都无法赞同。
行首加分号的规则比函数表达式后面加分号的规则其实要简单!
这是他推荐不加分号风格的依据,可是这个结论太过武断。他的推理过程是这样的:
1 | var a = function () { |
var a = function () {} 后面要加分号,坑爹!
var 声明的变量后面本来就要加分号,这里只不过是定义了一个 function 类型的变量而已,当然要加。
function if for 结构的 {} 后面不加分号,var a = function() {} 后面就要加分号,这样不一致。
本来就是不同类型的语句,只要了解 JavaScript 语法,这不是什么问题。
行首是否要加分号,我只要看本行的第一个字符就可以了……然而,你如果要判断“}”后面是否要加“;”,你得向上回溯,看清楚整段代码是一个结构呢?还是一个函数?如果是函数的话,是函数声明呢?还是函数表达式!许多时候,你可能向上翻几页还没找到对应的“{”!或者已经忘记了是几层缩进了!
这是编写可维护代码的问题。首先,你不应该写超长的 function。其次,缩进很多也是要尽量避免的。如果你写了超长的函数或者超多的缩进,有没有分号都一样难以阅读。其实超长 function 只应该出现在封装模块的时候:
1 | (function(root, factory) { |
这种超长函数不会带来困扰:
- 这种情况下超长函数不会有那么多缩进。
- 包装的头尾可以通过构建工具来加。
- IDE 可以展开/收缩大括号。
漏加分号时 jslint 提示不智能。
这虽然会带来一定困扰,但是实际操作中几乎不会带来问题,jslint 报错时我们首先检查是不是漏了分号就可以了。他的文章还列举了一些奇葩换行导致的问题,但那同样是代码可维护性差的问题,不是加不加分号的问题。那种奇葩代码,不加分号一样难读。
编译器(代码分析器)完全可以知道哪里应该有EOS (End of Statement)。既然所有的分号其实可以由机器自行加上(无论是加在行首还是行尾),那么我们自己还要手写所有分号的意义到底在哪里?
这是他的文章最后写到的,也很奇葩。要不要加分号之所以有争议,就是因为 JavaScript 的自动插入分号机制并不健全。如果要依赖这个机制,就像前面说的,要加入额外的限定规则。
结论
- 如果涉及到改框架、造轮子等事情,需要同时了解加分号风格与不加分号风格。
- 我们总要有个选择,我推荐加分号风格。