You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
8.4 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# JavaScript语法预备篇到底要不要写分号呢
你好我是winter。
在我们介绍JavaScript语法的全局结构之前我们先要探讨一个语言风格问题究竟要不要写分号。
这是一个非常经典的口水问题,“加分号”党和“不写分号”党之间的战争,可谓是经久不息。
实际上行尾使用分号的风格来自于Java也来自于C语言和C++,这一设计最初是为了降低编译器的工作负担。
但是从今天的角度来看行尾使用分号其实是一种语法噪音恰好JavaScript语言又提供了相对可用的分号自动补全规则所以很多JavaScript的程序员都是倾向于不写分号。
这里要特意说一点,在今天的文章中,我并不希望去售卖自己的观点(其实我是属于“加分号”党),而是希望比较中立地给你讲清楚相关的知识,让你具备足够的判断力。
我们首先来了解一下自动插入分号的规则。
## 自动插入分号规则
自动插入分号规则其实独立于所有的语法产生式定义,它的规则说起来非常简单,只有三条。
* 要有换行符,且下一个符号是不符合语法的,那么就尝试插入分号。
* 有换行符,且语法中规定此处不能有换行符,那么就自动插入分号。
* 源代码结束处,不能形成完整的脚本或者模块结构,那么就自动插入分号。
这样描述是比较难以理解的,我们一起看一些实际的例子进行分析:
```JavaScript
let a = 1
void function(a){
console.log(a);
}(a);
```
在这个例子中第一行的结尾处有换行符接下来void关键字接在1之后是不合法的这命中了我们的第一条规则因此会在void前插入分号。
```JavaScript
var a = 1, b = 1, c = 1;
a
++
b
++
c
```
这也是个著名的例子我们看第二行的a之后有换行符后面遇到了++运算符a后面跟++是合法的语法但是我们看看JavaScript标准定义中有\[no LineTerminator here\]这个字样这是一个语法定义中的规则你可以感受一下这个规则的内容下一小节我会给你详细介绍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。
```JavaScript
(function(a){
console.log(a);
})()
(function(a){
console.log(a);
})()
```
这个例子是比较有实际价值的例子这里两个function调用的写法被称作IIFE立即执行的函数表达式是个常见技巧。
这段代码意图上显然是形成两个IIFE。
我们来看第三行结束的位置JavaScript引擎会认为函数返回的可能是个函数那么在后面再跟括号形成函数调用就是合理的因此这里不会自动插入分号。
这是一些鼓励不写分号的编码风格会要求大家写IIFE时必须在行首加分号的原因。
```JavaScript
function f(){
return/*
This is a return value.
*/1;
}
f();
```
在这个例子中return和1被用注释分隔开了。
根据JavaScript自动插入分号规则**带换行符的注释也被认为是有换行符**而恰好的是return也有\[no LineTerminator here\]规则的要求。所以这里会自动插入分号f执行的返回值是undefined。
## no LineTerminator here 规则
好了到这里我们已经讲清楚了分号自动插入的规则但是我们要想彻底掌握分号的奥秘就必须要对JavaScript的语法定义做一些数据挖掘工作。
no LineTerminator here规则表示它所在的结构中的这一位置不能插入换行符。
自动插入分号规则的第二条有换行符且语法中规定此处不能有换行符那么就自动插入分号。跟no LineTerminator here规则强相关那么我们就找出JavaScript语法定义中的这些规则。
![](https://static001.geekbang.org/resource/image/c3/ad/c3ffbc89e049ad1901d4108c8ad88aad.jpg)
为了方便你理解,我把产生式换成了实际的代码。
下面一段代码展示了带标签的continue语句不能在continue后插入换行。
```JavaScript
outer:for(var j = 0; j < 10; j++)
for(var i = 0; i < j; i++)
continue /*no LineTerminator here*/ outter
```
break跟continue是一样的break后也不能插入换行
```JavaScript
outer:for(var j = 0; j < 10; j++)
for(var i = 0; i < j; i++)
break /*no LineTerminator here*/ outter
```
我们前面已经提到过return和后自增、后自减运算符。
```JavaScript
function f(){
return /*no LineTerminator here*/1;
}
```
```JavaScript
i/*no LineTerminator here*/++
i/*no LineTerminator here*/--
```
以及throw和Exception之间也不能插入换行符
```JavaScript
throw/*no LineTerminator here*/new Exception("error")
```
凡是async关键字后面都不能插入换行符
```JavaScript
async/*no LineTerminator here*/function f(){
}
const f = async/*no LineTerminator here*/x => x*x
```
箭头函数的箭头前,也不能插入换行:
```JavaScript
const f = x/*no LineTerminator here*/=> x*x
```
yield之后不能插入换行
```JavaScript
function *g(){
var i = 0;
while(true)
yield/*no LineTerminator here*/i++;
}
```
到这里我已经整理了所有标准中的no LineTerminator here规则实际上no LineTerminator here规则的存在多数情况是为了保证自动插入分号行为是符合预期的但是令人遗憾的是JavaScript在设计的最初遗漏了一些重要的情况所以有一些不符合预期的情况出现需要我们格外注意。
## 不写分号需要注意的情况
下面我们来看几种不写分号容易造成错误的情况,你可以稍微注意一下,避免发生同样的问题。
### 以括号开头的语句
我们在前面的案例中,已经展示了一种情况,那就是以括号开头的语句:
```JavaScript
(function(a){
console.log(a);
})()/*这里没有被自动插入分号*/
(function(a){
console.log(a);
})()
```
这段代码看似两个独立执行的函数表达式,但是其实第三组括号被理解为传参,导致抛出错误。
### 以数组开头的语句
除了括号,以数组开头的语句也十分危险:
```JavaScript
var a = [[]]/*这里没有被自动插入分号*/
[3, 2, 1, 0].forEach(e => console.log(e))
```
这段代码本意是一个变量a赋值然后对一个数组执行forEach但是因为没有自动插入分号被理解为下标运算符和逗号表达式我这个例子展示的情况甚至不会抛出错误这对于代码排查问题是个噩梦。
### 以正则表达式开头的语句
正则表达式开头的语句也值得你去多注意一下。我们来看这个例子。
```JavaScript
var x = 1, g = {test:()=>0}, b = 1/*这里没有被自动插入分号*/
/(a)/g.test("abc")
console.log(RegExp.$1)
```
这段代码本意是声明三个变量然后测试一个字符串中是否含有字母a但是因为没有自动插入分号正则的第一个斜杠被理解成了除号后面的意思就都变了。
注意,我构造的这个例子跟上面的例子一样,同样不会抛错,凡是这一类情况,都非常致命。
### 以Template开头的语句
以Template开头的语句比较少见但是跟正则配合时仍然不是不可能出现
```JavaScript
var f = function(){
return "";
}
var g = f/*这里没有被自动插入分号*/
`Template`.match(/(a)/);
console.log(RegExp.$1)
```
这段代码本意是声明函数f然后赋值给g再测试Template中是否含有字母a。但是因为没有自动插入分号函数f被认为跟Template一体的进而被莫名其妙地执行了一次。
## 总结
这一节课,我们讨论了要不要加分号的问题。
首先我们介绍了自动插入分号机制又对JavaScript语法中的no line terminator规则做了个整理最后我挑选了几种情况为你介绍了不写分号需要注意的一些常见的错误。
最后留给你一个问题,请找一些开源项目,看看它们的编码规范是否要求加分号,欢迎留言讨论。