天天看點

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

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;
}           

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

自動插入分号機制

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

有些時候,不合時宜地插入分号,會在例如return語句裡面導緻嚴重的後果。 

如果一個return語句要正确傳回一個值,這個值的表達式的開始部分必須和return位于同一行。

我們來看下面這個例子

return
{
    status:true;
}           

看起來這是要傳回一個包含對象,但是萬惡的自動插入分号處理後,傳回值變成了

undefined

,而且不會報任何的錯誤和警告。 

如果我們把大括号這樣處理的話就能避免這個問題

return{
    status:true;
};           

自動插入分号的詳細規則

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

前置條件

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

例子:

if(i>j)
else k=l           

這種情況下,if後面else前面是被解析為空語句,是以不加分号 

2.如果插入分号後,它會成為

for

語句頭部的兩個分号之一,那麼也不會插入分号 

例如:

for(a;b
)           

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

基本規則

從左向右解析程式的時候,當遇到一個不符合任何文法産生式的

token

也就是違規标記的時候,那麼隻要滿足下列條件之一,就會在哪個标記之前自動插入一個分号 

1、前一個标記和這個違規标記之前至少存在一個行終止符 

2、違規的标記是 

}

舉個栗子

{1
    2}3
{1
    ;2;}3           

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

}

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

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

受限的産生式隻限如下5個:

字尾表達式、continue語句、break語句、return語句、throw語句

如何預防這個毒瘤

1、字尾運算符 

++

 或 

--

 和它的操作數應該出現在同一行。 

2、

return

throw

語句的表達式開始位置應該和 

return

或 

throw

 token 同一行。 

3、

break

continue

 語句的标示符應該和 

break

 或

continue

token 同一行。

最重要的還是多加分号

來自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, '')
  }           
  • 5

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