JavaScript 的分號
JavaScript 的分號不管你要不省略分號,你都要理解 ASI 的運作原理,才能避免踩入陷阱。
同步發表於HackMD:JavaScript 的分號
在 JavaScript 中,分號 ; 是可選的,原因是 JavaScript 會幫我們自動加上分號,更確切的說法是語法解析器的 ASI(Automatic Semicolon Insertion,自動分號插入)斷行解讀並不是實際上插入分號。
自動插入分號的規則
不管你要不省略分號,你都要理解 ASI 的運作原理,才能避免踩入陷阱。
ECMAScript 標準定義的 ASI 包括 三條規則 和 兩條例外。
三條規則是描述何時該自動插入分號:
- 解析器從左往右解析程式碼(讀入
token),當碰到一個不能構成合法語句的token時,它會在以下幾種情況中在該token之前插入分號,此時這個不合群的token被稱為offending token:- 如果這個
token跟上一個token之間有至少一個換行。 - 如果這個
token是}。 - 如果前一個
token是),它會試圖把前面的token理解成do...while語句並插入分號。
- 如果這個
- 當解析到文件末尾發現語法還是有問題,就會在文件末尾插入分號。
- 當解析時碰到 restricted production 的語法(比如
return),並且在 restricted production 規定的[no LineTerminator here]的地方發現換行,那麼換行的地方就會被插入分號。
兩條例外表示,就算符合上述規則,如果分號會被解析成下面的樣子,它也不能被自動插入:
- 分號不能被解析成空語句。
- 分號不能被解析成
for語句頭部的兩個分號之一。
你一定會想,幹!誰看得懂?沒錯,沒範例說明誰看的懂。
1. 語彙單元 token
解析器在解析程式碼時,會把程式碼分成很多段 token,舉例來說:
var a = 10;
上面這行可以分成四段 token
var關鍵字a標識符=運算子12數字
解析器會在讀入一個一個 token 嘗試將其組成完整的語句(statement),當遇到 ; 就會認為這個語句結束了。那如果沒有 ; 就是依據定義的規則來插入分號,判斷語句。
任意 token 之間都可以插入一個或多個 Line Terminator(換行),不影響 JavaScript 的解析。
var
a
=
10
;
console.log(a); // 10
但如果不理解規則,任意換行就會遇到不如預期的錯誤,因為規則中,有根據換行來判斷是否插入分號的部分。
Esprima: Parser|在線 JavaScript 解析器
你可以輸入一些語句來看看token都是什麼。
2. Restricted production
Restricted production 是一組語法的統稱,它一共包含下面幾個語法:
- 後綴的
++和-- returncontinuebreakthrow- ES6 箭頭函數(參數和箭頭之間不能換行)
yield
這些語法都是在某個地方不能換行的。
3. 範例說明
依照規則依序說明。
3.1 換行
a
b
解析器讀取到 b 時,會發現無法構成合法的語句,然後 b 前面有換行,於是依照規則一的情況一,在 b 前面插入 ;。語句合法後,繼續解析,到了文件末端,b 還是無法構成合法語句,所以就依照規則二,在末端插入 ;。
最終會被解析成:
a
;b;
3.2 }
{ a } b
解析器讀取到 } 時,會發現 { a } 不合法,因為 a 為運算式,它必須使用 ; 結尾。但 offending token 是 }(規則一情況二),因此會在 } 前插入 ;。語句合法後繼續處理,解析到最後依照規則二,在末端插入 ;。
最終會被解析成:
{ a ;} b;
這裡要注意的是,{...} 屬於塊語句,本來就不需要 ;,因此不會變成:
{ a ;}; b;
3.3 do...while
do a; while(b) c
解析器讀取到 c 時,會發現語句不合法,但沒有換行也沒有 },但前面是 ) 所以會一句規則一情況三,將之前的 token 組成語句,判斷該語句是不是 do...while,如果是就會在前面插入分號。解析到最後依照規則二,在末端插入 ;。
最終會被解析成:
do a; while (b) ; c;
換句話說,do...while 最後的分號可以不用加,ASI 會自動幫你加入。
3.4 Restricted production
return 不能換行的原因就是因為規則三,有換行會自動插入分號。
return
a
會被解析成:
return;
a;
3.5 空語句
if (a)
else b
解析器分析到 else 時發現不合法,原本應該依照規一情況一,在前方加入分號,但這樣會變成空語句,這就是例外一,這個 ; 不會加入。
因此最後會拋出錯誤 // SyntaxError: Unexpected token else。
3.6 for
for (a; b
)
解析器讀到 ) 時發現不合法,原本應該依照規一情況一,在前方加入分號,但例外二,不能為 for() 內自動插入分號。
因此最後會拋出錯誤 // SyntaxError: Unexpected token else。
4. 規則總結
- 分號只會被插入到一個
}之前、一行的尾端,或是一個程式結尾。 - 只有在下一個
token無法被解析時,才會插入分號。 - 永不省略一個以
(、[、+、-、或/開頭的語句之前的分號(因為會與前一行一起被解析成合法語句)。 reurn、throw、break、continue、++、--後的語句不可以換行(換行會被自動插入分號)。- 分號永遠不會被插入到
for()內,或是插入作為空語句。
一定要使用分號的情況
1. for()
for() 語句中的三個運算式彼此之間,使用 ; 區隔。
for(var i = 0; i < 10; i++) {
//...
}
2. 同一行寫兩個語句(運算式)
var a = 0; a++
或是 switch 語句中的 case
case 'foo': doSomething(); break
3. 以 [ 或 ( 開頭的語句
其實是 (、[、+、-、或/ 開頭的語句,但 [ 或 ( 開頭的語句比較常見。
var x = 10
;(x + 10).toString()
或是立即函式:
var x = 42
;(function() {})()
不需要 與 不能加 的情況
1. for() 第三個運算式
這裡要注意的是,for() 第三個運算式:
for (var i = 0; i < 10; i++;){
//...
}
// Uncaught SyntaxError: Unexpected token ;
最後一個不需要加上分號,這是錯誤的語法。
如果你看過這種寫法:
for(var i = 0; i < 10;) {
i++
//...
}
那表示它省略了第三個運算式,將 i 的變化寫在 {} 內。
2. {} 區塊結尾
{} 表示一個區塊,所以 ; 不用加。
function func () {
//...
}
if () {
//...
}
for () {
//...
}
while () {
//...
}
雖然加了也不會報錯,因為會被解析成空語句。所以不建議加 ;,沒意義。
3. if()、for()、while()、或 switch()
if()、for()、while()、或 switch() 的 () 後方絕對不可以加入 ;,雖然合法,但控制邏輯是錯誤的。
if (0 === 1); { console.log('hi') }
這個 if() 變得與後面的 { alert('hi') } 沒關聯。
等同這樣:
if (0 === 1);
console.log('hi');
所以到底要不要加分號?
關於要不要加分號這裡就不討論了,有更多關於分號的文章,可以參考下方: