不為人知的JavaScript自動分號插入機制(ASI)

一坨翔發表於2018-08-14

JavaScript擁有自由的精神, ASI就是此精神的表現形式之一, ASI是Automatic semicolon insertion 的縮寫, 在許多語句後面可以省略分號, 當然很多小白還沒有發現這一點…首先分號是必不可少的, 因為回車符號在詞法分析階段就被全部殺掉了(特殊作用域除外),所以有了ASI有些人會認為回車符也是分隔符,其實回車符就是空白符,沒有任何意義….
 ASI的引入方便了開發者的同時也帶來了很多坑……..  本文介紹了自動插入分號機制在return語句中的例子,以及在es5標準下的相應規則。

楔子

之前一直寫C,寫了一段時間JavaScript之後一直很很好奇一個東西。在C和Java等語言裡面,大括號的使用一般都是類似這樣的

int main(args[])
{
    return 0;
}
  • 1
  • 2
  • 3
  • 4

而到JavaScript裡面則是這樣寫

function main(args){
    alert("hello");
    return 0;
}
  • 1
  • 2
  • 3
  • 4

起始的大括號不獨佔一行了,覺得很疑惑,查了一些資料才知道,這是和JavaScript一個自動修復機制有關係,它總是希望通過自動插入分號來修正有缺損的程式,雖然我不知道這有什麼用。

自動插入分號機制

在《JavaScript語言精粹》這本書裡,這個機制被劃入到了JavaScript的毒瘤裡面,與之並列的前面的全域性變數。

有些時候,不合時宜地插入分號,會在例如return語句裡面導致嚴重的後果。 
如果一個return語句要正確返回一個值,這個值的表示式的開始部分必須和return位於同一行。

我們來看下面這個例子

return
{
    status:true;
}
  • 1
  • 2
  • 3
  • 4

看起來這是要返回一個包含物件,但是萬惡的自動插入分號處理後,返回值變成了undefined,而且不會報任何的錯誤和警告。 
如果我們把大括號這樣處理的話就能避免這個問題

return{
    status:true;
};
  • 1
  • 2
  • 3

自動插入分號的詳細規則

在es5標準中定義了自動分號插入規則,包括以下三個基本規則加上兩個前置條件。

前置條件

1.如果插入分號後解析結果是空語句,那麼不會自動插入分號。 
例子:

if(i>j)
else k=l
  • 1
  • 2

這種情況下,if後面else前面是被解析為空語句,所以不加分號 
2.如果插入分號後,它會成為for語句頭部的兩個分號之一,那麼也不會插入分號 
例如:

for(a;b
)
  • 1
  • 2

這種情況下,雖然分行了,但是不會被插入分號。

基本規則

從左向右解析程式的時候,當遇到一個不符合任何語法產生式的token也就是違規標記的時候,那麼只要滿足下列條件之一,就會在哪個標記之前自動插入一個分號 
1、前一個標記和這個違規標記之前至少存在一個行終止符 
2、違規的標記是 }

舉個例子

{1
    2}3
{1
    ;2;}3
  • 1
  • 2
  • 3
  • 4

在第一行和第二行的1、2不符合任何產生式,且它們之間有一個行終止符,所以會在數字2之前加分號,在第二行2後面也需要加一個分號,因為後面的違規標記是一個}

左到右解析程式,tokens 輸入流已經結束,當解析器無法將輸入 token 流解析成單個完整 ECMAScript 程式 ,那麼就在輸入流的結束位置自動插入分號。

對於受限產生式,也就是下面的5個,我們把產生式 後面的 token 叫做受限 token,如果在 token 和 受限 token 間存在了至少一個行終止符,那麼會在受限 token 前自動加上 token。

受限的產生式只限如下5個:

字尾表示式continue語句break語句return語句throw語句

如何預防這個毒瘤

1、字尾運算子 ++ 或 -- 和它的運算元應該出現在同一行。 
2、returnthrow語句的表示式開始位置應該和 return或 throw token 同一行。 
3、break或 continue 語句的標示符應該和 break 或continuetoken 同一行。

最重要的還是多加分號

來自leviscar的小貼士 
為啥只執行函式前面要加分號? 
例如我之前看到的zepto.js的原始碼開頭

;(function(undefined) {
  if (String.prototype.trim === undefined) // fix for iOS 3.2
  String.prototype.trim = function() {
    return this.replace(/^s+|s+$/g, ``)
  }
  • 1
  • 2
  • 3
  • 4
  • 5

主要是應對程式碼合併壓縮時,由於缺少分號;帶來的錯誤。知道了上面的規則,在 ( 開頭的行前加分號就可以避免錯誤了。


相關文章