javascript语法:是否应该写分号呢?

360 阅读4分钟

自动插入分号规则

自动插入分号规则其实是独立于所有的语法产生的,他的规则说起来其实挺简单的,只有几条而已。

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

这样描述可能有点难以理解,我们下面看一些实例:

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

在这个实例中,第一行的结尾处有换行符,void关键字接在1之后是不合法的,这命中了我们上面的第一条规则,所以在void前面插入换行符。

 var a = 1, b = 1, c = 1;
a
++
b
++
c

其实,这也是个著名的实例,我们看到第二行的a之后,有换行符,遇到了++运算符,a后面跟++是合法的语法,但是我们看看javascript的标准定义中,有[no LineTerminator here]这个字样,这是一个语法定义中的规则,你可以感受一下这个规则的内容:

UpdateExpression[Yield, Await]:
LeftHandSideExpression[?Yield, ?Await]
LeftHandSideExpression[?Yield, ?Await][no LineTerminator here]++
LeftHandSideExpression[?Yield, ?Await][no LineTerminator here]--
++UnaryExpression[?Yield, ?Await]
--UnaryExpression[?Yield, ?Await]

于是,这里a的后面就要插入一个分号了。所以这段代码的最终结果,b和c都变成了2,而a还是1。

(function(a){
  console.log(a);
 })()
(function(a){
  console.log(a);
 })()

这个例子比较有实际价值,这里的function调用被称为立即执行的函数表达式,是个常见的技巧。

来看下第三行结束的位置,javascript引擎会认为函数返回值的可能是个函数,那么,后面跟括号形成函数的调用是合理的,因此这里不会自动插入分号。

function f(){
return/*
    This is a return value.
*/1;
}
f();

在代码中,return和1用注释分隔开了。

no LineTerminator here规则

好了,我们已经讲清楚了分号自动插入的规则,但是我们想要彻底掌握分号的奥秘,就必须要对javascript的语法定义做一些数据挖掘工作。

no LineTermiantor here规则表示它之所以在结构中的位置不能插入换行符。

自动插入分号的第二条:有换行符,且语法中规定此处不能有换行符,那么就自动插入分号。

下面我们用代码展示

outer:for(var j = 0; j < 10; j++)
   for(var i = 0; i < j; i++)
      continue /*no LineTerminator here*/ outter

break跟continue是一样的,break后也不能插入换行:

outer:for(var j = 0; j < 10; j++)
  for(var i = 0; i < j; i++)
     break /*no LineTerminator here*/ outter

前面说过return和后自增、后自减运算符。

function f(){
return /*no LineTerminator here*/1;
}

i/*no LineTerminator here*/++
i/*no LineTerminator here*/--

throw和Exception之间也不能插入换行符:

throw/*no LineTerminator here*/new Exception("error")

有async关键字,后面也不能插入关键字:

async/*no LineTerminator here*/function f(){

}
const f = async/*no LineTerminator here*/x => x*x

箭头函数的箭头前,也不能插入换行:

const f = x/*no LineTerminator here*/=> x*x

yield之后,不能插入换行:

function *g(){
  var i = 0;
  while(true)
    yield/*no LineTerminator here*/i++;
}

我们已经整理了no LineTerminator here规则没实际上no LineTerminator规则的存在,多数情况是为了保证自动插入分行行为是符合预期的,但是还是有一些不符合预期的情况出现,我们需要注意。

不写分号需要注意的情况

下面我们来看几种不写分号的情况: 以括号开头的语句:

(function(a){
console.log(a);
})()/* 这里没有被自动插入分号 */
(function(a){
console.log(a);
})()

这段代码看似是独立的函数表达式, 第三组括号被理解为传参,导致抛出错误。

以数组开头的语句

出了括号,以数组开头的语句也十分危险:

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

这段代码本意是一个变量a的赋值,然后对一个数组执行forEach,但是因为没有自动插入分号,被理解为下标运算符和逗号表达式,甚至不会抛出错误,对于新手来说代码排查是个很头痛问题。

正则表达式开头的语句

我们来看这个例子:

var x = 1, g = {test:()=>0}, b = 1/* 这里没有被自动插入分号 */
/(a)/g.test("abc")
console.log(RegExp.$1)

这段代码的本意是三个变量,然后测试一个字符串是否含有字母a,但是因为没有自动插入分号,正则的第一个斜杠被理解成了除号,后面的意思就发生了改变。

以Temlpate开头的语句

虽然不多见,但是还是会出现:

var f = function(){
 return "";
}
var g = f/* 这里没有被自动插入分号 */
`Template`.match(/(a)/);
console.log(RegExp.$1)

这段代码本意是声明函数f,然后赋值给g,最后测试Temlpate中是否含有字母a。但是因为没有自动插入分号,函数f被认为跟Temlpate一体的,进而执行了一次。