成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript ASI 機(jī)制詳解

frontoldman / 2911人閱讀

摘要:最近在清理的未讀列表,看到了才知道了的,一種自動插入分號的機(jī)制。這種行為被叫做自動插入分號,簡稱。不過在省略分號的風(fēng)格中,這種解析特性會導(dǎo)致一些意外情況。規(guī)則標(biāo)準(zhǔn)定義的包括三條規(guī)則和兩條例外。規(guī)則一情況三就是為量身定做的。

TL;DR

最近在清理 Pocket 的未讀列表,看到了 An Open Letter to JavaScript Leaders Regarding Semicolons 才知道了 JavaScript 的 ASI,一種自動插入分號的機(jī)制。因?yàn)槲沂?“省略分號風(fēng)格” 的支持者,之前也碰到過一次因?yàn)楹雎苑痔柈a(chǎn)生的問題,所以對此比較重視,也特意多看了幾份文檔,但越看心里越模糊。并不是我記不住 ( 和 [ 前面記得加 ; 這種結(jié)論,而是覺得看過的幾篇文章跟 ECMAScript 標(biāo)準(zhǔn)描述的有點(diǎn)區(qū)別。直到最近反復(fù)琢磨才突然有了 “原來如此” 的想法,于是就有了此文。

這篇文章會用 ECMAScript 標(biāo)準(zhǔn)的 ASI 定義來解釋它到底是如何運(yùn)作的,我會盡量用平易近人的方法描述它,避免官方文檔的晦澀。希望你跟我一樣有收獲。掌握 ASI 并不能夠讓你馬上解決手頭的問題,但能讓你成為一個更好的 JavaScript 程序員。

什么是 ASI

按照 ECMAScript 標(biāo)準(zhǔn),一些 特定語句(statement) 必須以分號結(jié)尾。分號代表這段語句的終止。但是有時候?yàn)榱朔奖?,這些分號是有可以省略的。這種情況下解釋器會自己判斷語句該在哪里終止。這種行為被叫做 “自動插入分號”,簡稱 ASI (Automatic Semicolon Insertion) 。實(shí)際上分號并沒有真的被插入,這只是個便于解釋的形象說法。

這些特定的語句有:

空語句

let

const

import

export

變量賦值

表達(dá)式

debugger

continue

break

return

throw

下面這段是我 個人的理解,上的定義同時也表示:

所有這些語句中的分號都是可以省略的。

除此之外其他的語句有兩種情況,一是不需要分號的(比如 if 和函數(shù)定義),二是分號不能省略的(比如 for),稍后會詳細(xì)介紹。

那么 ASI 如何知道在哪里插入分號呢?它會按照一些規(guī)則去判斷。但在說規(guī)則之前,我們先了解一下 JS 是如何解析代碼的。

Token

解析器在解析代碼時,會把代碼分成很多 token 。一個 token 相當(dāng)于一小段有特定意義的語法片段??匆粋€例子你就會明白:

var a = 12;

上面這段代碼可以分成四個 token :

var 關(guān)鍵字

a 標(biāo)識符

= 運(yùn)算符

12 數(shù)字

除此之外,(,. 等都算 token ,這里只是讓你有個大概的概念,比如 12 整個是一個 token ,而不是 12。字符串同理。

解釋器在解析語句時會一個一個讀入 token 嘗試構(gòu)成一個完整的語句 (statement),直到碰到特定情況(比如語法規(guī)定的終止)才會認(rèn)為這個語句結(jié)束了。記得上文提到的 變量賦值 這個語句必須以分號結(jié)尾么?這個例子中的終止符就是分號。用 token 構(gòu)成語句的過程類似于正則里的貪婪匹配,解釋器總是試圖用盡可能多的 token 構(gòu)成語句。

接下來是重點(diǎn):任意 token 之間都可以插入一個或多個換行符 (Line Terminator) ,這完全不影響 JS 的解析,所以上面的代碼可以寫成下面這樣(功能等價):

var
a
=
// = 和 12 之間有兩個換行符
12
;

這個特性可以讓開發(fā)者通過增加代碼的可讀性,更靈活地組織語言風(fēng)格。我們平時寫的跨多行的數(shù)組,字符串拼接,和鏈?zhǔn)秸{(diào)用都屬于這一類。不過在省略分號的風(fēng)格中,這種解析特性會導(dǎo)致一些意外情況。

比如這個例子中,以 / 開頭的正則會被理解成除法:

var a
  , b = 12
  , hi = 2
  , g = {exec: function() { return 3 }}

a = b
/hi/g.exec("hi")

console.log(a)
// 打印出 2, 因?yàn)榇a會被解析成:
//   a = b / hi / g.exec("hi");
//   a = 12 / 2 / 3

事實(shí)上這并不是省略分號的風(fēng)格的錯誤,而是開發(fā)者沒有理解 JS 解釋器的工作原理。如果你傾向省略分號的風(fēng)格,那了解 ASI 是必修課。

ASI 規(guī)則

ECMAScript 標(biāo)準(zhǔn)定義的 ASI 包括 三條規(guī)則兩條例外。

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

解析器從左往右解析代碼(讀入 token),當(dāng)碰到一個不能構(gòu)成合法語句的 token 時,它會在以下幾種情況中在該 token 之前插入分號,此時這個不合群的 token 被稱為 offending token :

如果這個 token 跟上一個 token 之間有至少一個換行。

如果這個 token 是 }

如果 前一個 token 是 ),它會試圖把前面的 token 理解成 do...while 語句并插入分號。

當(dāng)解析到文件末尾發(fā)現(xiàn)語法還是有問題,就會在文件末尾插入分號。

當(dāng)解析時碰到 restricted production 的語法(比如 return),并且在 restricted production 規(guī)定的 [no LineTerminator here] 的地方發(fā)現(xiàn)換行,那么換行的地方就會被插入分號。

兩條例外表示,就算符合上述規(guī)則,如果分號會被解析成下面的樣子,它也不能被自動插入:

分號不能被解析成空語句。

分號不能被解析成 for 語句頭部的兩個分號之一。

你會發(fā)現(xiàn)這些規(guī)則相當(dāng)晦澀,好像存心考你智商的,還有些坑爹的專有名詞。不要緊,我們來看幾個非常簡單的例子,看完之后你就會明白所有這些東西的含義。

例子解析 第一個例子:換行
a
b

我們模擬一下解析器的思考過程,大概是這樣的:解析器一個個讀取 token ,但讀到第二個 token b 時它就發(fā)現(xiàn)沒法構(gòu)成合法的語句,然后它發(fā)現(xiàn) b 和前面是有換行的,于是按照規(guī)則一(情況一),它在 b 之前插入分號變成 a ;b,這樣語句就合法了。然后繼續(xù)處理,這時讀到文件末了,b 還是不能構(gòu)成合法的語句,這時候按照規(guī)則二,它在末尾插入分號,結(jié)束。最終結(jié)果是:

a
;b;
第二個例子:大括號
{ a } b

解析器仍然一個個讀取 token ,讀到 token } 時發(fā)現(xiàn) { a } 是不合法的,因?yàn)?a 是表達(dá)式,它必須以分號結(jié)尾。但當(dāng)前 token 是 },所以按照規(guī)則一(情況二),它在 } 前面插入分號變成 { a ;},這句就通過了,然后繼續(xù)處理,按照規(guī)則二給 b 加上分號,結(jié)束。最終結(jié)果是:

{ a ;} b;

順帶一提,也許有人會覺得 { a; }; 這樣才更自然。但 {...} 屬于塊語句,而按照定義塊語句是不需要分號結(jié)尾的,不管是不是在一行。因?yàn)閴K語句也被用在其他地方(比如函數(shù)定義),所以下面這種代碼也是完全合法的,不需要任何分號:

function a() {} function b() {}
第三個例子:do while

這個是為了解釋規(guī)則一(情況三),這是最繞的部分,代碼如下:

do a; while(b) c

這個例子中解析到 token c 的時候就不對了。這里面既沒有換行也沒有 },但 c 前面是 ),所以解析器把之前的 token 組成一個語句,并判斷該語句是不是 do...while,結(jié)果正好是的!于是插入分號變成 do a; while(b) ;,最后給 c 加上分號,結(jié)束。最終結(jié)果為:

do a; while (b) ; c;

簡單點(diǎn)說,do...while 后面的分號是會自動插入的。但如果其他以 ) 結(jié)尾的情況就不行了。規(guī)則一(情況三)就是為 do...while 量身定做的。

第四個例子:return
return
a

你一定知道 return 和返回值之間不能換行,因?yàn)樯厦娲a會解析成:

return;
a;

但為什么不能換行?因?yàn)?return 語句就是一個 restricted production。這是什么意思?它是一組有嚴(yán)格限定的語法的統(tǒng)稱,這些語法都是在某個地方不能換行的,不能換行的地方會被標(biāo)注 [no LineTerminator here]

比如 ECMAScript 的 return 語法定義如下:

return [no LineTerminator here] Expression ;

這表示 return 跟表達(dá)式之間是不允許換行的(但后面的表達(dá)式內(nèi)部可以換行)。如果這個地方恰好有換行,ASI 就會自動插入分號,這就是規(guī)則三的含義。

剛才我們說了 restricted production 是一組語法的統(tǒng)稱,它一共包含下面幾個語法:

后綴的 ++--

return

continue

break

throw

ES6 箭頭函數(shù)(參數(shù)和箭頭之間不能換行)

yield

這些不用死記,因?yàn)榘凑粘R?guī)書寫習(xí)慣,幾乎沒人會這樣換行的。順帶一提,continuebreak 后面是可以接 label 的。但這不在本文討論范圍內(nèi),有興趣可以自己探索。

第五個例子:后綴表達(dá)式
a
++
b

解析器讀到 token ++ 時發(fā)現(xiàn)語句不合法,因?yàn)楹缶Y表達(dá)式是不允許換行的,換句話說,換行的都不是后綴表達(dá)式。所以它只能按照規(guī)則一(情況一)在 ++ 前面加上分號來結(jié)束語句 a,然后繼續(xù)執(zhí)行,因?yàn)榍熬Y表達(dá)式并不是 restricted production ,所以 ++b 可以組成一條語句,然后按照規(guī)則二在末尾加上分號。最終結(jié)果為:

a
;++
b;
第六個例子:空語句
if (a)
else b

解釋器解析到 token else 時發(fā)現(xiàn)不合法,本來按照規(guī)則一(情況一),它在應(yīng)該加上分號變成 if (a) ;,但這樣 ; 就變成空語句了,所以按照例外一,這個分號不能加。程序在 else 處拋異常結(jié)束。Node.js 的運(yùn)行結(jié)果:

else b
^^^^

SyntaxError: Unexpected token else
第七個例子:for
for (a; b
)

解析器讀到 token ) 時發(fā)現(xiàn)不合法,本來換行可以自動插入分號,但按照例外二,不能為 for 頭部自動插入分號,于是程序在 ) 處拋異常結(jié)束。Node.js 運(yùn)行結(jié)果如下:

)
^

SyntaxError: Unexpected token )
如何手動測試 ASI

我們很難有辦法去測試 ASI 是不是如預(yù)期那樣工作的,只能看到代碼最終執(zhí)行結(jié)果是對是錯。ASI 也沒有手動打開或關(guān)掉去對比結(jié)果。但我們可以通過對比解析器生成的 tree 是否一致來判斷 ASI 加的分號是不是跟我們預(yù)期的一致。這點(diǎn)可以用 Esprima 在線解析器 完成。

拿這段代碼舉例子:

do a; while(b) c

Esprima 解析的 Syntax 如下所示(不需要看懂,記住大概樣子就行):

{
    "type": "Program",
    "body": [
        {
            "type": "DoWhileStatement",
            "body": {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "Identifier",
                    "name": "a"
                }
            },
            "test": {
                "type": "Identifier",
                "name": "b"
            }
        },
        {
            "type": "ExpressionStatement",
            "expression": {
                "type": "Identifier",
                "name": "c"
            }
        }
    ],
    "sourceType": "script"
}

然后我們把加上分號的版本輸入進(jìn)去:

do a; while(b); c;

你會發(fā)現(xiàn)生成的 Syntax 是一致的。這說明解釋器對這兩段代碼解析過程是一致的,我們并沒有加入任何多余的分號。

然后試試這個有多余分號的版本:

do a; while(b); c;; // 結(jié)尾多一個分號

Esprima 結(jié)果:

{
    "type": "Program",
    "body": [
        {
            "type": "DoWhileStatement",
            "body": {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "Identifier",
                    "name": "a"
                }
            },
            "test": {
                "type": "Identifier",
                "name": "b"
            }
        },
        {
            "type": "ExpressionStatement",
            "expression": {
                "type": "Identifier",
                "name": "c"
            }
        },
        {
            // 多出來一個空語句
            "type": "EmptyStatement"
        }
    ],
    "sourceType": "script"
}

你會發(fā)現(xiàn)多出來一條空語句,那么這個分號就是多余的。

結(jié)尾

如果看到這里,相信你對 ASI 和 JS 的解析機(jī)制已經(jīng)有所了解。也許你會想 “那我再也不省略分號了”,那我建議你看看參考資料里的鏈接。而且就我的經(jīng)驗(yàn),即使是分號的堅(jiān)持者,少數(shù)地方也會無意識地使用 ASI 。比如有時候忘了寫分號,或者寫迭代器中的單行函數(shù)時。下次我會說下對省略分號的風(fēng)格的看法,和如何用 ESLint 保證代碼風(fēng)格的一致性。

參考資料

ECMAScript: ASI
ECMAScript 標(biāo)準(zhǔn)定義。本文的概念和很多例子完全遵照它來寫的。但也強(qiáng)烈建議你自己看看。

JavaScript Semicolon Insertion Everything you need to know
關(guān)于 ASI 的解釋,略微學(xué)術(shù)化,講得很詳細(xì),也很客觀。

An Open Letter to JavaScript Leaders Regarding Semicolons
NPM 作者對 ASI 和兩種風(fēng)格的看法,這篇更注重個人觀點(diǎn)的表達(dá)。他是省略分號風(fēng)格的傾向者。

Esprima: Parser
一個在線 JS 解析器。你可以輸入一些語句來看看 token 都是什么。也可以通過 Tree 的變化來測試加不加分號的影響。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/87716.html

相關(guān)文章

  • 備胎的自我修養(yǎng)——趣談 JavaScript 中的 ASI (Automatic Semicolon

    摘要:行結(jié)束符之后的符號有二義性,使得該符號與上條語句能夠無縫對接,不導(dǎo)致語法錯誤。然而在中,有幾種特殊語句是不允許行結(jié)束符存在的。如果語句中有行結(jié)束符,會優(yōu)先認(rèn)為行結(jié)束符表示的是語句的結(jié)束,這在標(biāo)準(zhǔn)中稱為限制產(chǎn)生式。 showImg(https://segmentfault.com/img/bVmyZB); 什么是 ASI ? 自動分號插入 (automatic semicolon i...

    _ipo 評論0 收藏0
  • JavaScript中分號自動插入

    摘要:中分號自動插入轉(zhuǎn)譯自鏈接描述在中,分號自動插入機(jī)制允許在一行代碼結(jié)尾省略分號。比如分號自動插入規(guī)則分號插入只是一個術(shù)語。如果在這些位置遇到換行了,分號將被插入。 JavaScript中分號自動插入 轉(zhuǎn)譯自:鏈接描述在JavaScript中,分號自動插入機(jī)制允許在一行代碼結(jié)尾省略分號。你應(yīng)該養(yǎng)成一直書寫分號的習(xí)慣,與此同時掌握J(rèn)avaScript分號省略處理機(jī)制是十分重要的。因?yàn)檫@不僅有...

    dadong 評論0 收藏0
  • javascript代碼風(fēng)格指北

    摘要:這段代碼工作正常,盡管沒有用分號在某些場景下是很管用的,特別是,有時候可以幫助減少代碼錯誤。比如不好的寫法盡管這段代碼能正常工作,但代碼中我們應(yīng)盡量避免使用。前言 在我們平時工作中寫代碼是最頻繁的事情了,但我們的代碼真的好看嗎? 預(yù)計(jì)本文閱讀時間(10分鐘) 正文 1.1--語句結(jié)尾 我們來看一段代碼 //合法的代碼 var name = Dreams; function sayName(...

    546669204 評論0 收藏0
  • javascript時要不要省略分號?

    摘要:自動填補(bǔ)分號的規(guī)則在說要不要寫分號之前,先了解一下自動填補(bǔ)分號的規(guī)則。后來看到知乎上的作者尤雨溪和前端大神賀師俊的回答后,我對寫分號的想法完全顛覆了??偸菍懛痔柌⒉荒芡耆鉀Q缺陷如后換行會自動插入分號。 在打算寫這篇文章之前,我是一個分號黨,在寫這篇文章之后,可能會轉(zhuǎn)為無分號黨了。之前是寫分號是編輯器語法較檢所養(yǎng)成的強(qiáng)迫癥,現(xiàn)在觀念的轉(zhuǎn)變,是因?yàn)榭戳瞬簧俅笊竦挠懻摵?,覺得javascr...

    wupengyu 評論0 收藏0
  • JS分號自動插入的ASI機(jī)制

    摘要:規(guī)范理論標(biāo)準(zhǔn)定義了自動分號插入規(guī)則,包括以下三個基本規(guī)則加兩個前置條件前置條件如果插入分號后解析結(jié)果是空語句,那么不會自動插入分號。 規(guī)范理論 es5 標(biāo)準(zhǔn)定義了自動分號插入規(guī)則,包括以下三個基本規(guī)則加兩個前置條件: 前置條件 1、如果插入分號后解析結(jié)果是空語句,那么不會自動插入分號。 例子:(空語句,else 前不加分好) if (a > b) else c = d 2、如果插入分號...

    sunny5541 評論0 收藏0

發(fā)表評論

0條評論

frontoldman

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<