ECMAScript 行尾要不要加 Semicolon 一直是很爭議的議題,傳統都會加上 ;,但最近則發現越來越多 Project 都不加,如 Vue、Redux … 等,到底 ASI 是什麼 ? 行尾不加 ; 會有什麼問題嗎 ?
Version
macOS Catalina 10.15.4
VS Code 1.44.2
Quokka 1.0.287
ECMAScript 2020
ASI
let f = _ => {
let x = 1, y = 2;
return
x + y;
}
f() // ?
若將 x + y 換行,結果會回傳 undefined。

let f = _ => {
let x = 1, y = 2;
return;
x + y;
}
f() // ?
因為 ASI 會自動在 return 之後加上 ;,因此回傳 undefined。

let f = _ => {
return
{
title: 'FP in JavaScript'
}
}
f() // ?
若回傳 object literal 換行,結果也會回傳 undefined。

let f = _ => {
return;
{ title: 'FP in JavaScript' };
}
f() // ?
因為 ASI 會自動在 return 之後加上 ;,因此回傳 undefined。
ASI
Automatic Semicolon Insertion
ECMAScript 會自動在行尾加上;

ASI Exception
ASI 並不是在所有行尾都會加上 ;,以下是例外:
[]、{}、() 之中的行尾
let f = _ => {
let a = [
1,
2,
3
]
return a
}
f() // ?
在 [] 中行尾不會有 ASI。

let f = _ => {
let o = {
title: 'FP in JavaScript',
price: 100
}
return o
}
f() // ?
在 {} 中行尾不會有 ASI。
let f = (x,
y,
z
) => {
return x + y + z
}
f(1, 2, 3) // ?
在 () 中行尾不會有 ASI。

行尾為 . 或 ,
let f = o => {
return o.
price
}
f({ price: 100 }) // ?
行尾為 . 時不會有 ASI。

let f = () => {
let x = 1,
y = 2
return x + y
}
f() // ?
行尾為 , 時不會有 ASI。

結尾為 ++ 或 –
let f = x => {
++
x
return x
}
f(3) // ?
行尾為 ++ 時不會有 ASI。

let f = x => {
--
x
return x
}
f(3) // ?
行尾為 -- 時不會有 ASI。

for()、while()、do、if()、else 之後沒 {} 時
let f = _ => {
let result = []
for (let i = 0; i < 3; i++)
result.push(i)
return result
}
f() // ?
for() 內若只有一行可省略 {},此時行尾不會有 ASI。

let f = _ => {
let result = []
let i = 0
while (i < 3)
result.push(i++)
return result
}
f() // ?
while() 內若只有一行可省略 {},此時行尾不會有 ASI。

let f = _ => {
let result = []
let i = 0
do
result.push(i++)
while (i < 3)
return result
}
f() // ?
do() 內若只有一行可省略 {},此時行尾不會有 ASI。

let f = x => {
if (x === 1)
return x + 1
else
return x + 2
}
f(1) // ?
f(2) // ?
if() 與 else 內若只有一行可省略 {},此時行尾不會有 ASI。

下一行為 [、(、+、-、*、/、,、. 時
let f = _ => {
let a =
[1, 2, 3]
return a
}
f() // ?
下一行為 [ 時,此時行尾不會有 ASI。

let f = (x, y) => {
let a =
(x + y)
return a
}
f(2, 1) // ?
下一行為 ( 時,此時行尾不會有 ASI。

let f = (x, y) => {
let a = x
+ y
return a
}
f(2, 1) // ?
下一行為 + 時,此時行尾不會有 ASI。

let f = (x, y) => {
let a = x
* y
return a
}
f(2, 1) // ?
下一行為 * 時,此時行尾不會有 ASI。

let f = (x, y) => {
let a = x
/ y
return a
}
f(2, 1) // ?
下一行為 / 時,此時行尾不會有 ASI。

let f = o => {
return o
.price
}
f({ price: 100 }) // ?
下一行為 . 時,此時行尾不會有 ASI。

let f = _ => {
let x = 1
,y = 2
return x + y
}
f() // ?
下一行為 , 時,此時行尾不會有 ASI。

Must Use Semicolon
雖然有 ASI 讓我們減少使用 ; 機會,但有些場合卻一定要使用 ;。
for()
let f = _ => {
let result = []
for (let i = 0; i < 3; i++)
result.push(i)
return result
}
f() // ?
在 for() 內三個 expression 一定要用 ; 隔開。

Multiple Statement / Expression
let f = _ => {
let x = 0; x++
return x
}
f() // ?
let x = 0 與 x++ 兩個 statement 或 expression 要寫在同一行時,一定要使用 ; 隔開。

switch()
let f = x => {
let result = 0
switch(x) {
case 0: result = x + 1; break
case 1: result = x + 2; break
case 2: result = x + 3; break
default: result = x + 4
}
return result
}
f(0) // ?
f(1) // ?
f(2) // ?
f(3) // ?
case 內的 statement 若與 break 要寫在同一行,要使用 ; 隔開。

以 [ 開頭
let i = 1
[1, 2, 3].map(x => x + i) // ?
這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 [ 時不會加上 ;,因此 let i = 1 與 [1, 2, 3].map() 視為同一行。

let i = 1;
[1, 2, 3].map(x => x + i); // ?
傳統在行尾都加上 ; 一定沒問題。

let i = 1
;[1, 2, 3].map(x => x + i) // ?
在行首加上 ; 強制與上一行分開也行。
若使用 ASI,這是少數
;要加在行首例外

以 Backtick 開頭
let x = 123
`${x}` // ?
這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 [ 時不會加上 ;,因此 let x = 123 與 ${x} 視為同一行。

let x = 123;
`${x}`; // ?
傳統在行尾都加上 ; 一定沒問題。

let x = 123
;`${x}` // ?
在行首加上 ; 強制與上一行分開也行。
若使用 ASI,這是少數
;要加在行首例外

以 + 開頭
let x = '123'
+x // ?
這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 + 時不會加上 ;,因此 let x = 123 與 +x 視為同一行。

let x = '123';
+x; // ?
傳統在行尾都加上 ; 一定沒問題。

let x = '123'
;+x // ?
在行首加上 ; 強制與上一行分開也行。
若使用 ASI,這是少數
;要加在行首例外

IIFE
let inc = x => x + 1
(function() {
let result = inc(2)
console.log(result)
})()
這是 ECMAScript 行尾不加 ; 常遇到問題,IIFE 會在新的一行使用 () 刮起來,最後使用 () 執行之,因為之前提到 ASI exception 中,當下一行為 ( 時不會加上 ;,因此 IIFE 與 let inc = x => x + 1 視為同一行。

let inc = x => x + 1;
(function() {
let result = inc(2)
console.log(result)
})();
傳統在每行結尾都加上 ; 一定沒問題。

let inc = x => x + 1
;(function() {
let result = inc(2)
console.log(result)
})()
只在 ( 前加上 ; 強制與上一行分開也行。
若使用 ASI,這是少數
;要加在行首例外

let inc = x => x + 1
void function() {
let result = inc(2)
console.log(result)
}()
若你覺得 ; 放在最前面很怪,也可以改用 void operator,它也會執行 IIFE,也不須再行首加 ;。

Must Not Use Semicolon
有些場景剛好相反,一定不能使用 ;。
for()
let f = _ => {
let result = []
for (let i = 0; i < 3; i++;)
result.push(i)
return result
}
f() // ?
for() 在括號內最後一個 expression 之後不能加上 ;。

let f = _ => {
let result = []
for (let i = 0; i < 3;)
result.push(i++)
return result
}
f() // ?
for() 內若只寫兩個 expression 之後加上 ; 是合法的,因為其省略第三個 expression。

() 之後
let f = x => {
if (x === 1); {
return 2
}
}
f(1) // ?
f(2) // ?
在 () 之後加上 ; 於 syntax 合法,但邏輯是錯的。

let f = x => {
if (x === 1);
return 2
}
f(1) // ?
f(2) // ?
在 () 之後加上 ; 相當於分開兩個 statement。

May Use Semicolon
有些場景 ; 可加可不加,一般建議不加。
{} 之後
function f() {
return 1
}
f() // ?
Function declaration 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

let x = 1
if (x === 1) {
x = 2
}
x // ?
if 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

let f = x => {
let result = 0
switch(x) {
case 0: result = x + 1; break
default: result = x + 4
}
return result
}
f(0) // ?
switch 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

Summary
看到以上這麼多規則可能都暈了,事實上只有兩條規則
- ECMAScript 行尾不用加
; - 一行以
[、(、+、-、*、/、,、.、backtick 開頭,且與上一行無關時,則要在上一行行尾加上;,或在本行行首加上;
這個例外常出現在以下情況:
- 以
[]array literal 開頭使用 Array.prototype 的 method,且與上一行無關時 - 以 backtick 開頭做 template string,且與上一行無關時
- 以
+開頭將 String 轉 Number,且與上一行無關時 - 以
()做 IIFE,且與上一行無關時
其他幾乎就很少遇到例外,可安心使用 ASI。
Conclusion
- ECMAScript 並不是全部都在行尾加上
;就了事,有些情況是不能加; - 反對行尾不加
;者多半以[]在一行開頭與 IIFE 例外為主要反對理由,實務上會遇到的例外大概只有 4 個 - ECMAScript 本來就不完美,但若要因為少數例外而因噎廢食,放棄原本美意的 ASI 則相當可惜
Reference
Eddy Chang, JavaScript 裡的語句用分號結尾是個選項嗎 ?
Dr. Axel Rauschmayer, Speaking JavaScript