JavaScript 的分號

JavaScript 的分號不管你要不省略分號,你都要理解 ASI 的運作原理,才能避免踩入陷阱。

同步發表於HackMD:JavaScript 的分號

在 JavaScript 中,分號 ; 是可選的,原因是 JavaScript 會幫我們自動加上分號,更確切的說法是語法解析器的 ASI(Automatic Semicolon Insertion,自動分號插入)斷行解讀並不是實際上插入分號。


自動插入分號的規則

不管你要不省略分號,你都要理解 ASI 的運作原理,才能避免踩入陷阱。

ECMAScript 標準定義的 ASI 包括 三條規則兩條例外

11.9.1 Rules of Automatic Semicolon Insertion

三條規則是描述何時該自動插入分號:

  1. 解析器從左往右解析程式碼(讀入 token),當碰到一個不能構成合法語句的 token 時,它會在以下幾種情況中在該 token 之前插入分號,此時這個不合群的 token 被稱為 offending token
    • 如果這個 token 跟上一個 token 之間有至少一個換行。
    • 如果這個 token}
    • 如果前一個 token),它會試圖把前面的 token 理解成 do...while 語句並插入分號。
  2. 當解析到文件末尾發現語法還是有問題,就會在文件末尾插入分號。
  3. 當解析時碰到 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 無法被解析時,才會插入分號。
  • 永不省略一個以 ([+-、或/ 開頭的語句之前的分號(因為會與前一行一起被解析成合法語句)。
  • reurnthrowbreakcontinue++-- 後的語句不可以換行(換行會被自動插入分號)。
  • 分號永遠不會被插入到 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');

所以到底要不要加分號?

關於要不要加分號這裡就不討論了,有更多關於分號的文章,可以參考下方:

TOP