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 是一組語法的統稱,它一共包含下面幾個語法:
- 後綴的
++
和--
return
continue
break
throw
- 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');
所以到底要不要加分號?
關於要不要加分號這裡就不討論了,有更多關於分號的文章,可以參考下方: