资讯专栏INFORMATION COLUMN

JavaScript里的语句用分号结尾是个选项吗

stormzhang / 3146人阅读

摘要:在以下的种情况是用回车或换行,是不会作自动插入分号来让语句作结尾。以下情况必用分号。但有例外,赋值时可以加分号是对的语法。

起因

这个文章一开始回覆于这篇回答中: javascript初级问题

也有之前的朋友写信来问,因为在读到我个人写的一本电子书: 从ES6开始的JavaScript学习生活,繁体,gitbook。我在写作风格里有说明,这本电子书中的范例都是使用"不用分号(;)作为代码语句的结尾"的风格。

所以我把所有的内容整理出来到这篇文章,并针对一些常见的反对问题作解说回答。

觉得写得好的朋友请分享给你的朋友,或点个推荐。这些知识是很基本的,但可惜一直被忽视。

前言

先说明我并没有要大家都来不加分号,而是回答"为什么可以不加分号",或是"为何分号是选项可有可无?",或是"分号是在何时可以不加?何时又一定要加?何时又算多加了?"等问题。

"不用分号作语句结尾"并不是"完全不使用分号",而是该加的时候加,不该加的时候不加,不用分号作为语句结尾。实际上,我如果在改写别人的代码或是像原本函式库(如jquery)的风格是有用分号(;)时,为了统一撰写风格,也是会加的,基本上自己最近写的ES6、React、Redux等就不用分号(;)结尾。

分号(;)的作用

在JS里的分号(;)是什么作用?总结一下,它既是语句(表达式)的分隔,也可以作为语句的结尾。

但只认为分号(;)就一定是语句的结尾是有疑问的,下面这两个例子就是典型范例,也是很常见的错误:

//有问题的例子
function add() {
  var a = 1, b = 2;
  return
    a + b;
}

或是像下面这个,也是很常见的错误示范:

//有问题的例子
function test() {
  return
    {
      test: true
    };
}

这两例的函数回传值必为undefined,而不是应该回传的值3与物件。为何?因为你有看到return先换行(回车),再接著要回传的值了吗?也就是说这两个代码在执行时,相当于下面这样的代码(我只写一个出来,另一例相同):

function test() {
  return;
  { test: true };
}

return因为先换了一行,视为语句结尾,所以根本没有可回传的值,也就是在上例子中,回车( )先把return结尾掉了,后面你加的分号(;)是结尾到下一行的。这逻辑代表回车( )在这一句语句结尾这功用上,比分号(;)还优先。

那为何回车( )或换行,可以作为语句的结尾?来源是由于ECMAScript的自动插入分号(Automatic Semicolon Insertion, 以下簡稱ASI)的标准。在语句或一段代码叙述后,加了回车( )后,JS剖析器会在执行期间自动帮你插入分号,这样理解了吗?所以不是回车( )的事,是因为自动帮你加分号作结尾了。

自动插入分号(ASI)这个规则一直在网上有争议,有一群人认为它是个容易造成误解的设计,有一群人认为它这设计是正确的,应该多多利用,就像不需要在一般情况下再多此一举,在语句后面加上分号(;)。不管你是支援哪一种,我认为都需要理解其中的内容,作为一个称职的JS开发者,这是很基础的一些知识。

自动插入分号(ASI)的5种例外情况

这里讨论的就是用回车( )或断行来作为语句的结尾,自动插入分号(Automatic Semicolon Insertion)标准的特例情况。在以下的5种情况是用回车( )或换行,是不会作自动插入分号来让语句作结尾。虽是说"例外情况",但大概也是每天都看得到,只是常不知道为何会这样而已,看了你就知道了。大致说明如下:

1. 当这一行的语句是没关闭的情况

例如数组(阵列)、对象(物件)文字字面、圆括号之类,或是最后是个点号(.)或逗号(,)时,也就是如果把这行结尾,就会产生不合法语句的情况。

注: 遇到等号(=)算未完整的表述式,如果指定值在下一行也会略过自动插入分号结尾作用

这种例子很常见,通常是在分开内容太长的语句,或是让值写得清楚的地方。我举下面的两个合法语法范例,你会很容易理解回车( )会在何时自动结尾,虽然这些例子是不好阅读的例子,一般情况我们会用来分隔比较长的语句:

//第一例
var a =
[ 1,
2,
3,
] 

//第二例
function test(a,
b,
c){
console.log(a,b,c
)
}
2. 这一行是++或--

这规则是很怪异的,从来没看过有人这样写过。JS会认为++与--在这一行是要准备来运算执行下一行的值,所以不结尾。下面是个合法范例:

a=1
--
a
console.log(a)
3. 这一行是for()、while()、do、if()或else,但没有用花括号({})时

这简写语法也很常见,每天都会用到,是个当花括号({})区块中执行代码不多时用的语法。以下为范例:

if(null==undefined)
   console.log(true)
else
   console.log(false)
4. 下一行的开头是([)、(()、(+)、(*)、(/)、(-)、(,)、(.),或二进位运算子(例如~ & |),可以与这行组成一个表达式时

以下为范例:

function test(){
  return 1
         +2
         -3
}
console.log(test())        

下面这个例子刚好与第一点颠倒的写法,也很常见,也是一个用于长语句的写法。有人喜欢用这种的写法,有些人喜欢用第一点的写法:

var a = 
[1
,2
,] 
5: 空白语句,不会自动插入分号作结尾

这与上面第3点有相关,有时候会看到有人犯了这个常见错误。下面是个错误例子,不论if这行与else这行间有没有换行,或是换了几百行,都不会自动插入分号作结尾:

//错误例子
var i = 10

if (i === 5)

else  console.log(false)

不过,你可以加上分号在if(...)这行最后,让语法合法可被执行。但真心没见过有人这样用,控制流程逻辑不对才会这样写。下面为合法例子:

//合法例子
var i = 10

if (i === 5);

else  console.log(false)

以上为用回车(n)作为语句结尾的的5个例外情况,其他都会自动帮你插入分号作结尾。

所以依这个解说,最一开始的例子中的return后面多了一个换行就会产生错误的回传值。与return有同样情况的还有几个,像continuebreakthrow等四个关键字,这些如果先换行,然后在下一行再加分号也是会变成两个不同的语句,因为在换行时已经自动插入分号结尾掉。

一个小常识是,分号(;)除了作为语句结尾或分隔外,它与JS中的其他符号有差异,是它自己本身可以形成一个合法的语句(表达式)。你可以在程式码里只输入一个分号(;),它是合法而不会报错的,其他的符号(例如:,:+-*/%)都会报错。

一定要使用分号的情况

分号(;)的用处不是只有语句结尾,它在某些语法中,具有分隔表述式或语句的功用。以下情况必用分号(;)。

1. for语句圆括号(())中的三个表述式彼此之间:

很常用到,不多加说明了,以下为例子:

for(var i=0 ; i<10 ; i++)
{
 //... 
}
2. 在同一行写两个语句(表述式)在一起,中间需要分号(;)区隔。

所以像switch语句中的case,如果break(或continue)要写在同一行时,break(或continue)前必加分号(;)。这在语句很短的时很会用到。以下为例子:

//第一例
var i = 0; i++ 

//第二例
case "foo": doSomething(); break
3. 以[(开头的行,前面需要加分号(;)

这是很特别一种撰写风格。实际上是一种保护防范的语法,有时候解译器或压缩工具会误认为某行语句,有开头(的话是准备要作函数呼叫,或开头[是准备要作数组(阵列)或对象(物件)的存取属性的事。所以会与上几行粘在一起剖析,造成代码执行结果不对或报错。

还有另一情况是IIFE,因为IIFE刚好也是用(开头。所以这两种开头的语句前,必加分号(;)在语句的最前面,以免造成误判。不过大概也只有这一个需要特别注意,IIFE你用到的情况有可能会多些。以下为例子:

//第一例
;(x || y).doSomething()
;[a, b, c].forEach(doSomething)

//第二例
var x = 42
;(function () { })()

参考: http://stackoverflow.com/ques...

"不需要"与"一定不能"使用分号的情况

接著要理解,什么时候必"不需要"与"一定不能"使用分号(;),这也很容易理解的。如果你是都要用分号来作每行语句的结尾,你应该了解一下。

1. for语句的最后一个(第三个)表达式后面

画蛇添足不多说,一定会造成错误,其实这错误很难犯,只是网上其他文章有列,所以我把它也列出来。

//错误语法
for (var i = 0; i < 10; i++;){}
2. 花括号的结尾符号(})的后面。但有例外,赋值时可以加分号(;)是对的语法。

这个规则理论上应该是"不需要"加而已,经测试在Chrome上也不会报错,算是不建议的语法,这也是个画蛇添足。

//不建议语法
function a(){};
if(){};

//正确语法,赋值时使用
var obj = {a:1};
var fun = function(){};

//正确语法,do...while
do {...} while (...);
3. 在if、for、while、或switch的圆括号结尾符号())后面加上分号(;)

这个是合法语法,但整个控制逻辑是错掉的,这错误很也不容易犯到。可以对照到最上面一节的第5个例外情况看看。

//错误例子,不过它合法
if (0 === 1); { alert("hi") }

//上例相当于下面这样
if (0 === 1);
alert("hi");
反对的原因,以及说明

以下列出几个常见的反对"不使用分号作为语句结尾"的原因,以这些原因的解说。不过这些原因,大部份都是因为FUD(惧、惑、疑),而不是真正以理解或科学实证的角度。

因为浏览器相容问题。旧版的浏览器不支持。

自动插入分号(ASI)的标准是何时加到ECMAScript的?

自动插入分号其实是本来就有的语言特性,网上可以找到的2000年的ECMAScript版本3,也就是ES3标准,裡面就有这个Automatic Semicolon Insertion章节了。2000年到现在也十多年了,你说要多旧的浏览器才真的不支持,说真的我也找不到。

根据网上的文章指出,曾经有一小段时间IE6这个在ASI实作上有问题,但后来MS很快修正了,现在我们能用到的IE6是支持的。下图是在WindowsXP SP3中的IE6浏览器中的小小测试,你可以仔细看一下,执行的代码并没有用分号来作语句结尾,而且是直接写在HTML文档中。

所以,你是听谁说旧版的浏览器不支援的?该不会那个人又说他也是听说的。谣言止于智者对吧。

因为压缩工具不支持

这原因以前的确存在过,有个压缩工具叫JSMin,是大师Douglas Crockford写的工具,如果你没用分号作语句结尾,它是连压都不压。当然这是因为Crockford他本来就不太赞成不用分号作语句结尾的关系。

现在老早就没有这问题了,其他常用的工具如Closure Compiler或webpack中的压缩外挂工具都可以压好压满,要不然怎么会有知名的一些大专案(bootstrap, npm, vue.js)也舍弃用分号作语句结尾的情况。

最近的在JSMin与Bootstrap中的一段代码争议在这里可以看到,JS发明人Brendan Eich的言论立场是会中立些,你有兴趣可以进一步看这篇: https://brendaneich.com/2012/...

语句都用分号(;)作结尾是一个好的撰写风格

最有名的是从JSLint这个检查工具开始的,它会检查你的每个语句的最后是不是用分号(;)来作结尾,如果不是会提出警告信息。

而提倡在每个语句后面一定要用分号来作结尾的大师级人物,最有名的算是JSON格式发明者Douglas Crockford,他的这个文章上有说明,从文章中可以看得出他是反对只使用ASI特性的。JSLint工具也是他作的,相信也有很多人看过他的大作"JavaScript: 优良部份"。不过,他也提倡了很多风格,例如tab键相当于4个空格,但现在一般都用2个空格,或是一行一个var变量定义,而且要按照英文字母排列,不知道有多少人真的是一定这样作。大师耳提面命的东西,我们小小程序员当然必定是要重视,但并不是照单全收。

回到我们的问题中,在浏览器上能顺利执行的代码语法,为何要检查工具要强迫认为是有问题的语法?

如果你都到处都加上分号(;),却还会发生有错误的情况(例如最上面的典型例子),那代表起因是来自于对于语言本身的无知,而不能怪罪于语言本身的臭虫或设计问题。

现在流行的其他检查工具,例如ESLint、JSHint等等,就算有这项检查也有选项可以关闭。这纯粹是开发团队或个人的选择才是正确的。

结语

要不要用分号(;)作为语句的结尾,就视个人习惯或组织团队规定的撰写风格了。不管你的选择为何,都应该理解为何分号(;)是选项而非必要的原因,不要因为大神或社群上大家说要加该加,就埋头拼命加,而不去理解其中的原因。

真实情况是并不是所有的情况分号作为语句都是选项,是有规则标准的。js虽然常常有坑,但本质上是死的东西,标准就已经定在那里了,上面我所说的规则学好足以应付9成情况,也可以让你的代码写起来更加有信心。

说句实在话,如果你是个能把加分号(;)的行为减到最少情况,也就是不使用分号作为语句的结尾的程序员,是因为你理解分号(;)在JS中的意义,以及ASI标准的规则。并不是标新立异或追求潮流,也不是为了要少打那几个符号,更不是为了要让js档压缩后会变小,这纯粹是个笑话,完全不会变小,因为压缩工具实际上会帮你加上分号。

最后几句,开源专案中真的完全不使用分号(;)的专案最近多起来的现象,但最有名的是大家每天都在用的npmbootstrap,还有最近比较火红的vue.jsredux这几个,有兴趣可以找找。

参考

http://blog.izs.me/post/23534...

http://mislav.net/2010/05/sem...

http://inimino.org/~inimino/b...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91099.html

相关文章

  • JavaScript ASI 机制详解

    摘要:最近在清理的未读列表,看到了才知道了的,一种自动插入分号的机制。这种行为被叫做自动插入分号,简称。不过在省略分号的风格中,这种解析特性会导致一些意外情况。规则标准定义的包括三条规则和两条例外。规则一情况三就是为量身定做的。 TL;DR 最近在清理 Pocket 的未读列表,看到了 An Open Letter to JavaScript Leaders Regarding Semico...

    frontoldman 评论0 收藏0
  • 备胎的自我修养——趣谈 JavaScript 中的 ASI (Automatic Semicolon

    摘要:行结束符之后的符号有二义性,使得该符号与上条语句能够无缝对接,不导致语法错误。然而在中,有几种特殊语句是不允许行结束符存在的。如果语句中有行结束符,会优先认为行结束符表示的是语句的结束,这在标准中称为限制产生式。 showImg(https://segmentfault.com/img/bVmyZB); 什么是 ASI ? 自动分号插入 (automatic semicolon i...

    _ipo 评论0 收藏0
  • php-cs-fixer - PHP 编码格式化工具

    摘要:是个代码格式化工具,格式化的标准是以及一些的标准。这个工具也和等优秀的库出自同门。如果同时设定了和,前者的优先级更高。同时使用和命令可以显示出需要修改的汇总,但是并不实际修改。你可以设置格式化的选项级别文件以及目录。 php-cs-fixer 是个代码格式化工具,格式化的标准是 PSR-1、PSR-2 以及一些 symfony 的标准。这个工具也和 symfony、twig 等优秀的 ...

    ityouknow 评论0 收藏0
  • JavaScript 编写规范

    摘要:如果你想了解更多关于强制类型转换的信息,你可以读一读的这篇文章。在只使用的情况下,所带来的强制类型转换使得判断结果跟踪变得复杂,下面的例子可以看出这样的结果有多怪了明智地使用真假判断当我们在一个条件语句中使用变量或表达式时,会做真假判断。 说明 如果本文档中有任何错误的、不符合行规的,敬请斧正。 引言 不管有多少人共同参与同一项目,一定要确保每一行代码都像是同一个人编写的。...

    MartinDai 评论0 收藏0
  • JavaScript 函数的定义

    摘要:关键词必须是小写的,并且必须以与函数名称相同的大小写来调用函数。当调用函数时,这些标识符则指代传入函数的实参。函数表达式其实是忽略函数名称的,并且不可以使用函数名这种形式调用函数。注意构造函数无法指定函数名称,它创建的是一个匿名函数。 一、关于函数 JavaScript函数是指一个特定代码块,可能包含多条语句,可以通过名字来供其他语句调用以执行函数包含的代码语句。 比如我们有一个特定的...

    mudiyouyou 评论0 收藏0

发表评论

0条评论

stormzhang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<