JavaScript的語(yǔ)句可以不用分號(hào)結(jié)尾么?

前言

先說(shuō)明我并沒(méi)有要大家都來(lái)不加分號(hào),而是回答"為什么可以不加分號(hào)",或是"為何分號(hào)是選項(xiàng)可有可無(wú)?",或是"分號(hào)是在何時(shí)可以不加?何時(shí)又一定要加?何時(shí)又算多加了?"等問(wèn)題。

"不用分號(hào)作語(yǔ)句結(jié)尾"并不是"完全不使用分號(hào)",而是該加的時(shí)候加,不該加的時(shí)候不加,不用分號(hào)作為語(yǔ)句結(jié)尾。實(shí)際上,我如果在改寫(xiě)別人的代碼或是像原本函式庫(kù)(如jquery)的風(fēng)格是有用分號(hào)(;)時(shí),為了統(tǒng)一撰寫(xiě)風(fēng)格,也是會(huì)加的,基本上自己最近寫(xiě)的ES6、React、Redux等就不用分號(hào)(;)結(jié)尾。

分號(hào)(;)的作用

在JS里的分號(hào)(;)是什么作用?總結(jié)一下,它既是語(yǔ)句(表達(dá)式)的分隔,也可以作為語(yǔ)句的結(jié)尾。

但只認(rèn)為分號(hào)(;)就一定是語(yǔ)句的結(jié)尾是有疑問(wèn)的,下面這兩個(gè)例子就是典型范例,也是很常見(jiàn)的錯(cuò)誤:

//有問(wèn)題的例子function add() {

var a = 1, b = 2;

return

a + b;

}

或是像下面這個(gè),也是很常見(jiàn)的錯(cuò)誤示范:

//有問(wèn)題的例子function test() {

return

{

test: true

};

}

這兩例的函數(shù)回傳值必為 undefined ,而不是應(yīng)該回傳的值 3 與物件。為何?因?yàn)槟阌锌吹絩eturn先換行(回車(chē)),再接著要回傳的值了嗎?也就是說(shuō)這兩個(gè)代碼在執(zhí)行時(shí),相當(dāng)于下面這樣的代碼(我只寫(xiě)一個(gè)出來(lái),另一例相同):

function test() {

return;

{ test: true };

}

return 因?yàn)橄葥Q了一行,視為語(yǔ)句結(jié)尾,所以根本沒(méi)有可回傳的值,也就是在上例子中,回車(chē)(\n)先把 return 結(jié)尾掉了,后面你加的分號(hào)(;)是結(jié)尾到下一行的。這邏輯代表回車(chē)(\n)在這一句語(yǔ)句結(jié)尾這功用上,比分號(hào)(;)還優(yōu)先。

那為何回車(chē)(\n)或換行,可以作為語(yǔ)句的結(jié)尾?來(lái)源是由于ECMAScript的自動(dòng)插入分號(hào)(Automatic Semicolon Insertion, 以下簡(jiǎn)稱ASI)的標(biāo)準(zhǔn)。在語(yǔ)句或一段代碼敘述后,加了回車(chē)(\n)后,JS剖析器會(huì)在執(zhí)行期間自動(dòng)幫你插入分號(hào),這樣理解了嗎?所以不是回車(chē)(\n)的事,是因?yàn)樽詣?dòng)幫你加分號(hào)作結(jié)尾了。

自動(dòng)插入分號(hào)(ASI)這個(gè)規(guī)則一直在網(wǎng)上有爭(zhēng)議,有一群人認(rèn)為它是個(gè)容易造成誤解的設(shè)計(jì),有一群人認(rèn)為它這設(shè)計(jì)是正確的,應(yīng)該多多利用,就像不需要在一般情況下再多此一舉,在語(yǔ)句后面加上分號(hào)(;)。不管你是支援哪一種,我認(rèn)為都需要理解其中的內(nèi)容,作為一個(gè)稱職的JS開(kāi)發(fā)者,這是很基礎(chǔ)的一些知識(shí)。

自動(dòng)插入分號(hào)(ASI)的5種例外情況

這里討論的就是用回車(chē)(\n)或斷行來(lái)作為語(yǔ)句的結(jié)尾,自動(dòng)插入分號(hào)(Automatic Semicolon Insertion)標(biāo)準(zhǔn)的特例情況。在以下的4種情況是用回車(chē)(\n)或空一行是不會(huì)作自動(dòng)插入分號(hào)來(lái)讓語(yǔ)句作結(jié)尾,雖是說(shuō)"特例",但大概也是每天都看得到,只是常不知道為何會(huì)這樣而已,看了你就知道了。大致說(shuō)明如下:

1. 當(dāng)這一行的語(yǔ)句是沒(méi)關(guān)閉的情況

例如數(shù)組(陣列)、對(duì)象(物件)文字字面、圓括號(hào)之類(lèi),或是最后是個(gè)點(diǎn)號(hào)(.)或逗號(hào)(,)時(shí),也就是如果把這行結(jié)尾,就會(huì)產(chǎn)生不合法語(yǔ)句的情況。

注: 遇到等號(hào)(=)算未完整的表述式,如果指定值在下一行會(huì)略過(guò)自動(dòng)結(jié)尾作用

這種例子很常見(jiàn),通常是在分開(kāi)內(nèi)容太長(zhǎng)的語(yǔ)句,或是讓值寫(xiě)得清楚的地方。我舉下面的兩個(gè)合法語(yǔ)法范例,你會(huì)很容易理解回車(chē)(\n)會(huì)在何時(shí)自動(dòng)結(jié)尾,雖然這些例子是不好閱讀的例子:

//第一例var a =

[ 1,2,3,

]

//第二例function test(a,

b,

c){console.log(a,b,c

)

}

2. 這一行是++或--

這規(guī)則是很怪異的,從來(lái)沒(méi)看過(guò)有人這樣寫(xiě)過(guò)。JS會(huì)認(rèn)為++與--在這一行是要準(zhǔn)備來(lái)運(yùn)算執(zhí)行下一行的值,所以不結(jié)尾。下面是個(gè)合法范例:

a=1--

a

console.log(a)

3. 這一行是for()、while()、do、if()或else,但沒(méi)有用花括號(hào)({})時(shí)

這簡(jiǎn)寫(xiě)語(yǔ)法也很常見(jiàn),每天都會(huì)用到,是個(gè)當(dāng)執(zhí)行代碼不多時(shí)的寫(xiě)法。以下為范例:

if(null==undefined)

console.log(true)else

console.log(false)

4. 下一行的開(kāi)頭是([)、(()、(+)、(*)、(/)、(-)、(,)、(.),或二進(jìn)位運(yùn)算子(例如~ & |),可以與這行組成一個(gè)表達(dá)式時(shí)

以下為范例:

function test(){

return 1

+2

-3

}console.log(test())

下面這個(gè)例子剛好與第一點(diǎn)顛倒的寫(xiě)法,也很常見(jiàn),有人喜歡用這種的寫(xiě)法,有些人喜歡用第一點(diǎn)的:

var a =

[1

,2

,]

5: 空白語(yǔ)句不會(huì)自動(dòng)結(jié)尾

這與上面第3點(diǎn)有相關(guān),有時(shí)候會(huì)看到有人犯了這個(gè)常見(jiàn)錯(cuò)誤。下面是個(gè)錯(cuò)誤例子,不論 if 這行與 else 這行間有沒(méi)有換行,或是換了幾百行,都不會(huì)自動(dòng)結(jié)尾:

//錯(cuò)誤例子var i = 10

if (i === 5)

else console.log(false)

你可以加上分號(hào)在 if(...) 這行最后,讓語(yǔ)法合法可被執(zhí)行。但真心沒(méi)見(jiàn)過(guò)有人這樣用,控制流程邏輯不對(duì)才會(huì)這樣寫(xiě)。下面為合法例子:

//合法例子var i = 10

if (i === 5);

else console.log(false)

以上為用回車(chē)(n)作為語(yǔ)句結(jié)尾的的5個(gè)例外情況,其他都會(huì)自動(dòng)幫你插入分號(hào)作結(jié)尾。

所以依這個(gè)解說(shuō),最一開(kāi)始的例子中的 return 后面多了一個(gè)換行就會(huì)產(chǎn)生錯(cuò)誤的回傳值。與 return 有同樣情況的還有幾個(gè),像 continue 、 break 、 throw等,這些如果先換行,然后在下一行再加分號(hào)也是會(huì)變成兩個(gè)不同的語(yǔ)句,因?yàn)樵趽Q行時(shí)已經(jīng)自動(dòng)插入分號(hào)。

一個(gè)小常識(shí)是,分號(hào)(;)除了作為語(yǔ)句結(jié)尾或分隔外,它與JS中的其他符號(hào)有個(gè)差異,它自己本身可以形成一個(gè)合法的語(yǔ)句(表達(dá)式)。你可以在程式碼里只輸入一個(gè)分號(hào)(;),它是合法而不會(huì)報(bào)錯(cuò)的,其他的符號(hào)(例如: ,:+-/%* )都會(huì)報(bào)錯(cuò)。

一定要使用分號(hào)的情況

分號(hào)(;)的用處不是只有語(yǔ)句結(jié)尾,它在某些語(yǔ)法中,具有分隔表述式或語(yǔ)句的功用。以下情況必用分號(hào)(;)。

1. for語(yǔ)句圓括號(hào)( () )中的三個(gè)表述式彼此之間:

很常用到,不多加說(shuō)明了,以下為例子:

for(var i=0 ; i<10 ; i++)

{

//...

}

2. 在同一行寫(xiě)兩個(gè)語(yǔ)句(表述式)在一起,中間需要分號(hào)(;)區(qū)隔。

所以像 switch 語(yǔ)句中的 case ,如果 break (或 continue )要寫(xiě)在同一行時(shí), break (或 continue )前必加分號(hào)(;),以下為例子:

//第一例var i = 0; i++

//第二例case 'foo': doSomething(); break

3. 以 [ 或 ( 開(kāi)頭的行,前面需要加分號(hào)(;)

這是很特別一種撰寫(xiě)風(fēng)格。實(shí)際上是一種保護(hù)防范的語(yǔ)法,有時(shí)候解譯器或壓縮工具會(huì)誤認(rèn)為某行語(yǔ)句,有開(kāi)頭 ( 的話是準(zhǔn)備要作函數(shù)呼叫,或開(kāi)頭 [ 是準(zhǔn)備要作數(shù)組(陣列)或?qū)ο?物件)的存取屬性的事。

還有另一情況是IIFE,因?yàn)镮IFE剛好也是用 ( 開(kāi)頭。所以這兩種開(kāi)頭的語(yǔ)句前,必加分號(hào)(;)在語(yǔ)句的最前面,以免造成誤判。不過(guò)大概也只有這一個(gè)需要特別注意,IIFE你用到的情況有可能會(huì)多些。

以下為例子:

//第一例

;(x || y).doSomething()

;[a, b, c].forEach(doSomething)

//第二例var x = 42

;(function () { })()

參考: http://stackoverflow.com/ques...

"不需要"與"一定不能"使用分號(hào)的情況

接著要理解,什么時(shí)候必"不需要"與"一定不能"使用分號(hào)(;),這也很容易理解的。如果你是都要用分號(hào)來(lái)作每行語(yǔ)句的結(jié)尾,你應(yīng)該了解一下。

1. for 語(yǔ)句的最后一個(gè)(第三個(gè))表達(dá)式后面

畫(huà)蛇添足不多說(shuō),一定會(huì)造成錯(cuò)誤,其實(shí)這錯(cuò)誤很難犯,只是網(wǎng)上有我把它列出來(lái)。

//錯(cuò)誤語(yǔ)法for (var i=0; i < 10; i++;){}

2. 花括號(hào)的結(jié)尾(})的后面。但有例外,賦值時(shí)可以加分號(hào)(;)是對(duì)的語(yǔ)法。

這個(gè)規(guī)則理論上應(yīng)該是"不需要"加而已,經(jīng)測(cè)試在Chrome上也不會(huì)報(bào)錯(cuò),算是不建議的語(yǔ)法,這也是個(gè)畫(huà)蛇添足。

//不建議語(yǔ)法function a(){};if(){};

//正確語(yǔ)法,賦值時(shí)使用var obj = {a:1};var fun = function(){};

//正確語(yǔ)法,do...whiledo {...} while (...);

3. 在if、for、while、或switch的圓括號(hào)結(jié)尾后面加上分號(hào)(;)

這個(gè)是合法語(yǔ)法,但整個(gè)邏輯錯(cuò)掉,而且這錯(cuò)誤很也不易犯。可以對(duì)照到最上面一節(jié)的第5個(gè)例外情況看看。

//錯(cuò)誤例子,不過(guò)它合法if (0 === 1); { alert("hi") }

//上例相當(dāng)于下面這樣if (0 === 1);alert("hi");

反對(duì)的原因,以及說(shuō)明

以下針對(duì)幾個(gè)常見(jiàn)的反對(duì)不使用分號(hào)作為語(yǔ)句結(jié)尾的理由,以我的所知回答這些原因。不過(guò),大部份都是因?yàn)镕UD(懼、惑、疑),而不是真正以理解或科學(xué)實(shí)證的角度。

因?yàn)闉g覽器相容問(wèn)題。舊版的瀏覽器不支持。

自動(dòng)插入分號(hào)(ASI)的標(biāo)準(zhǔn)是何時(shí)加到ECMAScript的?

自動(dòng)插入分號(hào)其實(shí)是本來(lái)就有的語(yǔ)言特性,網(wǎng)上可以找到的2000年的ECMAScript版本3,也就是ES3標(biāo)準(zhǔn),里面就有這個(gè) Automatic Semicolon Insertion 章節(jié)了。

根據(jù)網(wǎng)上的文章指出,曾經(jīng)有一小段時(shí)間IE6這個(gè)在ASI實(shí)作上有問(wèn)題,但后來(lái)很快修正了,現(xiàn)在我們能用到的IE6是支持的。下圖是在WindowsXP SP3中的IE6小小測(cè)試的,你可以仔細(xì)看一下,并沒(méi)有用分號(hào)來(lái)作語(yǔ)句結(jié)尾。

image

所以,你是聽(tīng)誰(shuí)說(shuō)舊版的瀏覽器不支援的?該不會(huì)那個(gè)人又說(shuō)他也是聽(tīng)說(shuō)的。謠言止于智者對(duì)吧。

因?yàn)閴嚎s工具不支持

這原因以前的確存在過(guò),有個(gè)壓縮工具叫JSMin,是大師Douglas Crockford寫(xiě)的工具,印象中如果你沒(méi)用分號(hào)作語(yǔ)句結(jié)尾,它是連壓都不壓,當(dāng)然Crockford他個(gè)人本來(lái)就不太贊成不用分號(hào)作語(yǔ)句結(jié)尾的關(guān)系。

現(xiàn)在老早就沒(méi)有這問(wèn)題了,其他常用的工具如Closure Compiler或webpack中的外掛工具都可以壓好壓滿,要不然怎么會(huì)有大專(zhuān)案(bootstrap, npm, vue.js)也舍棄用分號(hào)作語(yǔ)句結(jié)尾。

最近的在JSMin與Bootstrap中的一段代碼爭(zhēng)議在這里可以看到,JS發(fā)明人Brendan Eich的言論立場(chǎng)是會(huì)中立些: https://brendaneich.com/2012/...

語(yǔ)句都用分號(hào)(;)作結(jié)尾是一個(gè)好的撰寫(xiě)風(fēng)格

最有名的是從 JSLint 這個(gè)檢查工具開(kāi)始的,它會(huì)檢查你的每個(gè)語(yǔ)句的最后是不是用分號(hào)(;)來(lái)作結(jié)尾,如果不是會(huì)提出警告信息。

而提倡在每個(gè)語(yǔ)句后面一定要用分號(hào)來(lái)作結(jié)尾的大師級(jí)人物,最有名的算是JSON格式發(fā)明者Douglas Crockford, 他的這個(gè)文章 上有說(shuō)明,從文章中可以看得出他是反對(duì)ASI特性的。JSLint工具也是他作的,相信也有很多人看過(guò)他的大作"JavaScript: 優(yōu)良部份"。不過(guò),他也提倡了很多風(fēng)格,例如tab鍵相當(dāng)于4個(gè)空格,但現(xiàn)在一般都用2個(gè)空格,或是一行一個(gè)var變量定義,而且要按照英文字母排列。大師說(shuō)的我們小小程序員當(dāng)然必定是要重視,但并不是照單全收。

回到我們的問(wèn)題中,在瀏覽器上能順便執(zhí)行的代碼語(yǔ)法,為何要強(qiáng)制被檢查工具認(rèn)為是有問(wèn)題的語(yǔ)法?

如果你都到處都加上分號(hào)(;),卻還會(huì)發(fā)生有錯(cuò)誤的情況(例如最上面的典型例子),那這代表起因是來(lái)自于對(duì)于語(yǔ)言本身的設(shè)計(jì)或標(biāo)準(zhǔn)的無(wú)知,而不是怪罪于語(yǔ)言本身的臭蟲(chóng)或設(shè)計(jì)問(wèn)題。

現(xiàn)在流行的其他檢查工具,例如ESLint、JSHint等等,就算有這項(xiàng)檢查也有選項(xiàng)可以關(guān)閉。這純粹是開(kāi)發(fā)團(tuán)隊(duì)或個(gè)人的選擇才是正確的。

結(jié)語(yǔ)

要不要用分號(hào)(;)作為語(yǔ)句的結(jié)尾,就視個(gè)人習(xí)慣或組織團(tuán)隊(duì)規(guī)定的撰寫(xiě)風(fēng)格了。不管你的選擇為何,都應(yīng)該理解為何分號(hào)(;)是選項(xiàng)而非必要的原因,不要因?yàn)榇笊窕蛏缛荷洗蠹艺f(shuō)要加該加,就埋頭拼命加,而不去理解其中的原因。

真實(shí)情況是并不是所有的情況分號(hào)作為語(yǔ)句都是選項(xiàng),是有規(guī)則標(biāo)準(zhǔn)的。js雖然常常有坑,但本質(zhì)上是死的東西,標(biāo)準(zhǔn)就已經(jīng)定在那里了,上面我所說(shuō)的規(guī)則學(xué)好足以應(yīng)付9成情況,也可以讓你的代碼寫(xiě)起來(lái)更加有信心。

最后幾句,開(kāi)源專(zhuān)案中真的完全不使用分號(hào)(;)的專(zhuān)案最近多起來(lái)的現(xiàn)象,但最有名的是大家每天都在用的 npmbootstrap ,還有最近比較火紅的 vue.jsredux這幾個(gè),有興趣可以找找。

文章來(lái)源:SegmentFault

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容