关于要不要写分号;

  • 关于要不要写分号的问题

“究竟要不要加分号”已经是一个争论很久的问题了,实际上,行尾使用分号的风格来自于 Java,也来自于 C 语言和 C++,这一设计最初是为了降低编译器的工作负担。

但是,在今天来看,行尾使用分号貌似是一种”麻烦”,恰好 JavaScript 语言又提供了相对可用的分号自动补全规则,所以,很多 JavaScript 的程序员都是倾向于不写分号(我就是不写分号党)。

先说说自动插入分号规则,自动插入分号规则其实独立于所有的语法产生式定义,它的规则说起来非常简单,只有三条:

  • 要有换行符,且下一个符号是不符合语法的,那么就尝试插入分号;
  • 有换行符,且语法中规定此处不能有换行符,那么就自动插入分号;
  • 源代码结束处,不能形成完整的脚本或者模块结构,那么就自动插入分号。

举例一

1
2
3
4
let a = 1
void function(a){
console.log(a);
}(a);

在这个例子中,第一行的结尾处有换行符,接下来 void 关键字接在 1 之后是不合法的,这命中了我们的第一条规则,因此会在 void 前插入分号。

举例二

1
2
3
4
5
6
7
8
9
var a = 1, b = 1, c = 1;
a
++
b
++
c
console.log(a); // 结果输出为1
console.log(b); // 结果输出为2
console.log(c); // 结果输出为2

第二行的 a 之后,有换行符,后面遇到了 ++ 运算符,a 后面跟 ++ 是合法的语法,但是我们看看 JavaScript 标准定义中,有[no LineTerminator here]规则中一项:后自增,后自减运算符前不能插入换行,这是一个语法定义中的规则,下面有说道关于[no LineTerminator here]规则;所以再根据自动插入分号规则的第二条:有换行符,且语法中规定此处不能有换行符,那么就自动插入分号;
于是,这里 a 的后面就要插入一个分号了。所以这段代码最终的结果,b 和 c 都变成了 2,而 a 还是 1。

举例三

1
2
3
4
5
6
(function(a){ 
console.log(a);
})()
(function(a){
console.log(a);
})()

这两个function函数被叫做 立即执行函数表达式,看第三行结束的位置,JavaScript引擎会认为函数返回的也有可能是一个函数,比如:

1
2
3
4
(function(a){ 
return function () {} // 某函数
})()(function(a){})()
// 那么后面如果解析到还有括号,会被认为是函数的调用,姑后面有括号 会被认为是合理的

所以后面在跟括号形成函数的调用也是合理的,因此这里不会自动插入分号,所以该代码运行起来 就会报错:(intermediate value)(…) is not a function

这也是一些鼓励不写分号的编码风格会要求大家写 IIFE 时必须在行首加分号的原因,如下所示:

1
2
3
4
5
6
(function(a){ 
console.log(a);
})()
;(function(a){
console.log(a);
})()

举例四

1
2
3
4
5
6
function f() {
return/*
This is a return value.
*/1;
}
f()

在这个例子中,根据自动插入分号规则,带换行符的注释也被认为是有换行符,恰好 return 也有[no LineTerminator here]规则的要求,所以这里也会插入分号,所以函数返回值为 undefined。

接下来说说 no LineTerminator here 规则,no LineTerminator here 规则表示它所在的结构中的这一位置不能插入换行符,

自动插入分号规则的第二条:有换行符,且语法中规定此处不能有换行符,那么就自动插入分号。跟 no LineTerminator here 规则强相关,那么我们就找出 JavaScript 语法定义中的这些规则。

[no LineTerminator here] 规则 :

  • 带标签的continue语句,不能在continue后插入换行
  • 带标签的break语句,不能在break后插入换行
  • return后不能插入换行
  • 后自增,后自减运算符前不能插入换行
  • throw 和 Exception 之间不能插入换行
  • 凡是async关键字,后面不能插入换行
  • 剪头函数的箭头前,不能插入换行
  • yield之后,不能插入换行

不写分号需要注意的情况

最后梳理一些不写分号容易报错的情况

  1. 以括号开头的语句,比如上文中提到的自执行表达式

    1
    2
    3
    4
    5
    6
    (function(a){ 
    console.log(a);
    })() /*这里不会被自动插入分号,后面跟着的括号会被理解为传参,导致抛出错误。*/
    (function(a){
    console.log(a);
    })()
  2. 以数组开头的语句

    1
    2
    var a = [[]]/*这里没有被自动插入分号*/
    [3, 2, 1, 0].forEach(e => console.log(e))

[] 后面 还有[],被理解为下标运算符和逗号表达式,上面的例子甚至不会报错,代码排查起来非常恶心啊,为了展示报错,可以尝试

1
2
var a = []/*这里没有被自动插入分号*/
[3, 2, 1, 0].forEach(e => console.log(e))

这里就会因为解析的问题报错:Cannot read property ‘forEach’ of undefined,一个空数组后面跟上[],如果被理解为下标远算符,肯定是undefined

  1. 以正则表达式开头的语句
    1
    2
    3
    var x = 1, g = {test:()=>0}, b = 1/*这里没有被自动插入分号*/
    /(a)/g.test("abc") /*测试一个字符串中是否含有字母 a*/
    console.log(RegExp.$1)

这里正则的第一个斜杠如果被理解为除号,后面的意思就变了,直接报错:a is not defined

  1. 以 Template 开头的语句
    1
    2
    3
    var a = 'nihao' /*这里没有被自动插入分号*/
    `Template`.match(/(a)/)
    console.log(RegExp.$1)

这里 ‘nihao’ 会被认为跟 Template 一体的,进而被莫名其妙地执行了一次,报错为:”nihao” is not a function。

总结一句话,看到代码开头为 括号,方括号,斜杠,加号,减号都结合上下行多注意一下。

参考 winter 老师的javascript语法的讲解,受益匪浅!

Comentarios

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×