- Published on
JavaScript裡的語句用分號結尾是個選項嗎
- Authors
- Name
- Eddy Chang
起因
這個文章一開始回覆於這篇回答中: javascript初級問題
也有之前的朋友寫信來問,因為在讀到我寫的一本電子書: 從ES6開始的JavaScript學習生活,繁體,gitbook。我在寫作風格里有說明,這本電子書中的範例都是使用"不用分號(;)作為程式碼語句的結尾"的風格。
所以我把所有的內容整理出來到這篇文章,並針對一些常見的反對問題作解說回答。
覺得寫得好的朋友請分享給你的朋友。這些知識是很基本的,但可惜一直被忽視。
前言
先說明我並沒有要大家都來不加分號,而是回答"為什麼可以不加分號",或是"為何分號是選項可有可無?",或是"分號是在何時可以不加?何時又一定要加?何時又算多加了?"等問題。
"不用分號作語句結尾"並不是"完全不使用分號",而是該加的時候加,不該加的時候不加,不用分號作為語句結尾。實際上,我如果在改寫別人的原始碼或是像原本函式庫(如jquery)的風格是有用分號(;)時,為了統一撰寫風格,也是會加的,基本上自己最近寫的ES6、 React、Redux等就不用分號(;)結尾。
分號(;)的作用
在JS裡的分號(;)是什麼作用?總結一下,它既是語句(表達式)的分隔,也可以作為語句的結尾。
但只認為分號(;)就一定是語句的結尾是有疑問的,下面這兩個例子就是典型範例,也是很常見的錯誤:
//有問題的例子
function add() {
var a = 1, b = 2;
return
a + b;
}
或是像下面這個,也是很常見的錯誤示範:
//有問題的例子
function test() {
return
{
test: true
};
}
這兩例的函式回傳值必為undefined
,而不是應該回傳的值3
與物件。為何?因為你有看到return先換行(Enter鍵),再接著要回傳的值了嗎?也就是說這兩個代碼在執行時,相當於下面這樣的代碼(我只寫一個出來,另一例相同):
function test() {
return;
{ test: true };
}
return
因為先換了一行,視為語句結尾,所以根本沒有可回傳的值,也就是在上例子中,Enter鍵(\n)先把return
結尾掉了,後面你加的分號(;)是結尾到下一行的。這邏輯代表Enter鍵(\n)在這一句語句結尾這功用上,比分號(;)還優先。
那為何Enter鍵(\n)或換行,可以作為語句的結尾?來源是由於ECMAScript的自動插入分號(Automatic Semicolon Insertion, 以下簡稱ASI)的標準。在語句或一段代碼敘述後,加了Enter鍵(\n)後,JS剖析器會在執行期間自動幫你插入分號,這樣理解了嗎?所以不是Enter鍵(\n)的事,是因為自動幫你加分號作結尾了。
自動插入分號(ASI)這個規則一直在網上有爭議,有一群人認為它是個容易造成誤解的設計,有一群人認為它這設計是正確的應該多多利用,不需要在一般情況下再多此一舉,在語句後面加上分號(;)。不管你是支持哪一種,我認為都需要理解其中的內容,作為一個稱職的JS開發者,這是很基礎的知識。
自動插入分號(ASI)的5種例外情況
這裡討論的就是用Enter鍵(\n)或斷行來作為語句的結尾,自動插入分號(Automatic Semicolon Insertion)標準的特例情況。在以下的5種情況是用Enter鍵(\n)或換行,是不會作自動插入分號來讓語句作結尾。雖是說"例外情況",但大概也是每天都看得到,只是常不知道為何會這樣而已,看了你就知道了。大致說明如下:
1. 當這一行的語句是沒關閉的情況
例如陣列、物件文字字面、圓括號之類使用情況,或是最後是個點號(.)或逗號(,)時,也就是如果把這行結尾,就會產生不合法語句的情況。
注: 遇到等號(=)算未完整的表述式,如果指定值在下一行也會略過自動插入分號結尾作用
這種例子很常見,通常是在分開內容太長的語句,或是讓值寫得清楚的時候。我舉下面的兩個合法語法範例,你會很容易理解Enter鍵(\n)會在何時自動結尾,雖然這些例子看起來是不好閱讀的例子,一般情況我們會用來分隔比較長的語句:
//第一例
var a =
[ 1,
2,
3,
]
//第二例
function test(a,
b,
c){
console.log(a,b,c
)
}
2. 這一行是 ++ 或 --
這規則是很怪異的,從來沒看過有人這樣寫過。 JS會認為++與--在這一行是要準備來運算執行下一行的值,所以不結尾。下面是個合法範例:
a=1
--
a
console.log(a)
3. 這一行是for()、while()、do、if()或else,但沒有用花括號()時
這簡寫語法也很常見,每天都會用到,是個當花括號()區塊中執行代碼不多時用的語法。以下為範例:
if(null==undefined)
console.log(true)
else
console.log(false)
4. 下一行的開頭是([)、(()、(+)、(*)、(/)、(-)、(,)、(.),或二進位運算子(例如~ & |),可以與這行組成一個表達式時
以下為範例:
function test(){
return 1
+2
-3
}
console.log(test())
下面這個例子剛好與第一點顛倒的寫法,也很常見,也是一個用於長語句分隔的寫法。有人喜歡用這種的寫法,有些人喜歡用第一點的寫法:
var a =
[1
,2
,]
5. 空白語句,不會自動插入分號作結尾
這與上面第3點有相關,有時候會看到有人犯了這個常見錯誤。下面是個錯誤例子,不論if
這行與else
這行間有沒有換行,或是換了幾百行,都不會自動插入分號作結尾:
//錯誤例子
var i = 10
if (i === 5)
else console.log(false)
不過,你可以加上分號在if(...)
這行最後,讓語法合法可執行。但沒見過有人這樣用,大概要控制流程邏輯不對才會這樣寫。下面為合法例子:
//合法例子
var i = 10
if (i === 5);
else console.log(false)
以上為用Enter鍵(\n)作為語句結尾的的5個例外情況,其他都會自動幫你插入分號作結尾。
所以依這個解說,最一開始的例子中的return
後面多了一個換行就會產生錯誤的回傳值。與return
可能有相同情況的還有幾個,像continue
、break
、throw
等幾個關鍵字,這些如果先換行,然後在下一行再加分號也是會變成兩個不同的語句,因為在換行時已經自動插入分號結尾掉。
另一個小常識是,分號(;)除了作為語句結尾或分隔外,它與JS中的其他符號有差異,是它自己本身可以形成一個合法的語句(表達式)。你可以在程式碼裡只輸入一個分號(;),它是合法而不會有錯誤的,其他的符號(例如:,:+-*/%
)都會有錯誤。
一定要使用分號的情況
分號(;)的用處不是只有語句結尾,它在某些語法中,具有分隔表述式或語句的功用。以下情況必用分號(;)。
()
)中的三個表述式彼此之間:
1. for語句圓括號(很常用到不多說明,以下為例子:
for(var i=0 ; i<10 ; i++)
{
//...
}
2. 在同一行寫兩個語句(表述式)在一起,中間需要分號(;)區隔。
所以像switch
語句中的case
,如果break
(或continue
)要寫在同一行時,break
(或continue
)前必加分號(;)。這在語句很短的時很會用到。以下為例子:
//第一例
var i = 0; i++
//第二例
case 'foo': doSomething(); break
[
或(
開頭的行,前面需要加分號(;)
3. 以這是很特別一種撰寫風格。實際上是一種保護的語法,有時候解譯器或壓縮工具會誤認為某行語句,有開頭(
的話是準備要作函式呼叫,或開頭[
是準備要作陣列或物件的存取屬性的事。所以會與上幾行粘在一起剖析,造成代碼執行結果不對或報錯。
還有另一情況是IIFE,因為IIFE剛好也是用(
開頭。所以這兩種開頭的語句前,必加分號(;)在語句的最前面,以免造成誤判。不過大概也只有這一個需要特別注意,IIFE你用到的情況有可能會多些。以下為例子:
//第一例
;(x || y).doSomething()
;[a, b, c].forEach(doSomething)
//第二例
var x = 42
;(function () { })()
"不需要"與"一定不能"使用分號的情況
接著要理解,什麼時候必"不需要"與"一定不能"使用分號(;),這也很容易理解的。如果你是都要用分號來作每行語句的結尾,你應該了解一下。
for
語句的最後一個(第三個)表達式後面
1. 畫蛇添足不多說,一定會造成錯誤,其實這錯誤很難犯,只是網上其他文章有列,所以我把它也列出來。你如果會看到for的圓括號裡最後面會出現分號(;)的合法語法,應該只是省略第三個表達式的寫法。
//錯誤語法
for (var i = 0; i < 10; i++;){}
//合法語法
for (var i = 0; i < 10;){ i++ }
2. 花括號的結尾符號(})的後面。但有例外,賦值時可以加分號(;)是對的語法。
這個規則應該是"不需要"加而已,經測試在Chrome上也不會報錯,算是不建議的語法,這也是個畫蛇添足。
//不建議語法
function a(){};
if(){};
//正確語法,賦值時使用
var obj = {a:1};
var fun = function(){};
//正確語法,do...while
do {...} while (...);
)
)後面加上分號(;)
3. 在if、for、while、或switch的圓括號結尾符號(這個是合法語法,但整個控制邏輯是錯掉的,這錯誤很也不容易犯到。可以對照到最上面一節的第5個例外情況看看。
//錯誤例子,不過它合法
if (0 === 1); { alert("hi") }
//上例相當於下面這樣
if (0 === 1);
alert("hi");
反對的原因,以及說明
以下列出幾個常見的反對"不使用分號作為語句結尾"的原因,以這些原因的解說。不過這些原因,大部份都是因為FUD(懼、惑、疑),而不是真正以科學實證的角度來作為理由。
因為瀏覽器相容問題。舊版的瀏覽器不支持。
自動插入分號(ASI)的標準是何時加到ECMAScript的?
自動插入分號其實是本來就有的語言特性,網上可以找到的2000年的ECMAScript版本3,也就是ES3標準,裡面就有這個Automatic Semicolon Insertion
章節了。 2000年到現在也十多年了,你說要多舊的瀏覽器才真的不支持,說真的我也找不到。
根據網上的文章指出,曾經有一小段時間IE6這個在ASI實作上有問題,但後來MS很快修正了,現在我們能用到的IE6是支持的。下圖是在WindowsXP SP3中的IE6瀏覽器中的小小測試,你可以仔細看一下,執行的代碼並沒有用分號來作語句結尾,而且是直接寫在HTML文檔中。
所以,你是聽誰說舊版的瀏覽器不支援的?該不會那個人又說他也是聽說的。謠言止於智者對吧。
因為壓縮工具不支持
這原因以前的確存在過,有個壓縮工具叫JSMin,是大師Douglas Crockford寫的工具,如果你沒用分號作語句結尾,它是連壓都不壓。當然這是因為Crockford他本來就不太贊成不用分號作語句結尾的關係。
現在老早就沒有這問題了,其他常用的工具如Closure Compiler或webpack中的壓縮外掛工具都可以壓好壓滿,要不然怎麼會有知名的一些大專案(bootstrap, npm, vue.js)也捨棄用分號作語句結尾的情況。
最近的在JSMin與Bootstrap中的一段代碼爭議在這裡可以看到,JS發明人Brendan Eich的言論立場是會中立些,你有興趣可以進一步看這篇: https://brendaneich.com/2012/04/the-infernal-semicolon/
語句都用分號(;)作結尾是一個好的撰寫風格
最有名的是從JSLint這個檢查工具開始的,它會檢查你的每個語句的最後是不是用分號(;)來作結尾,如果不是會提出警告信息。
而提倡在每個語句後面一定要用分號來作結尾的大師級人物,最有名的算是JSON格式發明者Douglas Crockford,他的這個文章上有說明,從文章中可以看得出他是反對只使用ASI特性的。 JSLint工具也是他作的,相信也有很多人看過他的大作"JavaScript: 優良部份"。不過,他也提倡了很多風格,例如tab鍵相當於4個空格,但現在一般都用2個空格,或是一行一個var變量定義,而且要按照英文字母排列,不知道有多少人真的是一定這樣作。大師耳提面命的東西,我們小小程序員當然必定是要重視,但並不是照單全收。
回到我們的問題中,在瀏覽器上能順利執行的代碼語法,為何要檢查工具要強迫認為是有問題的語法?
如果你都到處都加上分號(;),卻還會發生有錯誤的情況(例如最上面的典型例子),那代表起因是來自於對於語言本身的無知,而不能怪罪於語言本身的臭蟲或設計問題。
現在流行的其他檢查工具,例如ESLint、JSHint等等,就算有這項檢查也有選項可以關閉。這純粹是開發團隊或個人的選擇。
結語
要不要用分號(;)作為語句的結尾,就視個人習慣或組織團隊規定的撰寫風格了。不管你的選擇為何,都應該理解為何分號(;)是選項而非必要的原因,不要因為大神或社群上大家說要加該加,就埋頭拼命加,而不去理解其中的原因。
真實情況並不是所有的情況分號作為語句都是選項,而是有規則標準的。JS雖然常常有陷阱,但本質上是固定的東西,標準就已經定在那裡了,上面所說的規則學好,足以應付9成情況,也可以讓你的代碼寫起來更加有信心。
說句實在話,如果你是個能把加分號(;)的行為減到最少情況,也就是不使用分號作為語句的結尾的開發者,是因為你理解分號(;)在JS中的意義,以及ASI標準的規則。並不是標新立異或追求潮流,也不是為了要少打那幾個符號,更不是為了要讓js檔壓縮後會變小,這純粹是個笑話,完全不會變小,因為壓縮工具會幫你加上分號。
最後幾句,開放原始碼專案中真的完全不使用分號(;)的專案最近有多起來的現象,但最有名的是大家每天都在用的npm
、bootstrap
,還有最近比較熱門的vue.js
、redux
這幾個,有興趣可以找找。