许多编程语言都有语句结束的概念。不过,用哪个符号结束以及结束语句的规则在各个语言之间存在差异。
对于 JavaScript 来说,它在这方面要求非常宽松。在 JavaScript 中语句结束符是分号,不过也可以不写。它是怎么做到的?JavaScript 编译器是如何知道何时该结束一条语句呢?
它是通过一套简单的规则和自动分号插入机制(Automatic Semicolon Insertion,简写为 ASI)实现的。我们先看一下分号的规则,然后了解一下 ASI 的机制。
分号的规则
分号用于标记一条语句的结束。
// Variable Declaration
const myVar = 5;
// Invoking a function
myFunction();
块语句的末尾应该有一个分号。
// function definition
function myFunction() {
// function logic
}
// A conditional `if` block
if (myCondition) {
// conditional logic
}
// loop constructs
while (myCondition) {
// conditional logic
}
分号的特殊案例。
const myFunction = function() {};
上面的代码是一个函数表达式,所以需要一个分号。
自动分号插入(Automatic Semicolon Insertion)
这是 Javascript 中的一种机制,它遵循一些规则,编译器会尝试把源代码分解成语句。默认情况下,ASI 总是打开的。有时候自动分号插入机制很有用,但偶尔会遇到 ASI 改变语义引起的棘手问题。
ASI 规则
1. 遇到行结束符时,会插入一个分号。
// 实际的源码
const myVar = 5
// ASI 之后
const myVar = 5;
2.遇到句法不允许的 ‘}’ 时插入一个分号。
{ 1
2 } 3
// ASI 后变成了
{ 1
;2 ;} 3;
3.遇到一个 restricted production 后跟行结束符时,自动插入一个分号。
这些 restricted production 包括:++, --, continue, break, return, throw, yield 和 module 关键字。解析器遇到这些关键字后跟一个行结束符时,会在关键字后面插入一个分号。
// 实际的源码
for (let i = 0; i < 5; i++) {
if (myCondition) {
continue
}
}
// ASI 后
for (let i = 0; i < 5; i++) {
if (myCondition) {
continue;
}
}
但是需要注意 return 关键字:
return
a + b
// ASI 后变成了
return;
a + b;
上述代码会在 return 语句后面插入一个分号,它会返回 undefined。return 后面的语句无法被访问。为了避免这个问题,可以把上述2条语句写在同一行。
依赖 ASI 可能导致的意外问题
如果我们不在代码中写分号,而是依赖 ASI,会偶尔遇到语义完全改变的情况。
意外的函数调用
// 实际的源码
const myResult = myVar1 + myVar2
(myVar3 + myVar4).toString()
// 期待的输出
const myResult = myVar1 + myVar2;
(myVar3 + myVar4).toString();
// ASI 后实际的输出
const myResult = myVar1 + myVar2(myVar3 + myVar4).toString()
意外的属性访问
// 实际的源码
const myResult = myFunction()
['ul', 'ol'].map(x => x + 1)
// 期望的输出
const myResult = myFunction();
['ul', 'ol'].map(x => x + 1)
// ASI 后实际的输出
const myResult = myFunction()[("ul", "ol")].map(x => x + 1);
结语
现在很多人选择忽略分号,让代码看起来更简洁。不过我建议始终明确写上分号,以避免引起潜在的问题。