亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

JavaScript ASI 機(jī)制詳解

frontoldman / 2981人閱讀

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

TL;DR

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

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

什么是 ASI

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

這些特定的語(yǔ)句有:

空語(yǔ)句

let

const

import

export

變量賦值

表達(dá)式

debugger

continue

break

return

throw

下面這段是我 個(gè)人的理解,上的定義同時(shí)也表示:

所有這些語(yǔ)句中的分號(hào)都是可以省略的。

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

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

Token

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

var a = 12;

上面這段代碼可以分成四個(gè) token :

var 關(guān)鍵字

a 標(biāo)識(shí)符

= 運(yùn)算符

12 數(shù)字

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

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

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

var
a
=
// = 和 12 之間有兩個(gè)換行符
12
;

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

比如這個(gè)例子中,以 / 開(kāi)頭的正則會(huì)被理解成除法:

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

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

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

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

ASI 規(guī)則

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

三條規(guī)則是描述何時(shí)該自動(dòng)插入分號(hào):

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

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

如果這個(gè) token 是 }。

如果 前一個(gè) token 是 ),它會(huì)試圖把前面的 token 理解成 do...while 語(yǔ)句并插入分號(hào)。

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

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

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

分號(hào)不能被解析成空語(yǔ)句。

分號(hào)不能被解析成 for 語(yǔ)句頭部的兩個(gè)分號(hào)之一。

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

例子解析 第一個(gè)例子:換行
a
b

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

a
;b;
第二個(gè)例子:大括號(hào)
{ a } b

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

{ a ;} b;

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

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

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

do a; while(b) c

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

do a; while (b) ; c;

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

第四個(gè)例子:return
return
a

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

return;
a;

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

比如 ECMAScript 的 return 語(yǔ)法定義如下:

return [no LineTerminator here] Expression ;

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

剛才我們說(shuō)了 restricted production 是一組語(yǔ)法的統(tǒng)稱(chēng),它一共包含下面幾個(gè)語(yǔ)法:

后綴的 ++--

return

continue

break

throw

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

yield

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

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

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

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

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

else b
^^^^

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

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

)
^

SyntaxError: Unexpected token )
如何手動(dòng)測(cè)試 ASI

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

拿這段代碼舉例子:

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

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

do a; while(b); c;

你會(huì)發(fā)現(xiàn)生成的 Syntax 是一致的。這說(shuō)明解釋器對(duì)這兩段代碼解析過(guò)程是一致的,我們并沒(méi)有加入任何多余的分號(hào)。

然后試試這個(gè)有多余分號(hào)的版本:

do a; while(b); c;; // 結(jié)尾多一個(gè)分號(hào)

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"
            }
        },
        {
            // 多出來(lái)一個(gè)空語(yǔ)句
            "type": "EmptyStatement"
        }
    ],
    "sourceType": "script"
}

你會(huì)發(fā)現(xiàn)多出來(lái)一條空語(yǔ)句,那么這個(gè)分號(hào)就是多余的。

結(jié)尾

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

參考資料

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

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

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

Esprima: Parser
一個(gè)在線(xiàn) JS 解析器。你可以輸入一些語(yǔ)句來(lái)看看 token 都是什么。也可以通過(guò) Tree 的變化來(lái)測(cè)試加不加分號(hào)的影響。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/87716.html

相關(guān)文章

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

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

    _ipo 評(píng)論0 收藏0
  • JavaScript中分號(hào)自動(dòng)插入

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

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

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

    546669204 評(píng)論0 收藏0
  • 寫(xiě)javascript時(shí)要不要省略分號(hào)?

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

    wupengyu 評(píng)論0 收藏0
  • JS分號(hào)自動(dòng)插入的ASI機(jī)制

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

    sunny5541 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<