為語言新增新功能的最佳方式是什麼?本章節說明 ECMAScript 6 採取的方法。它被稱為「一個 JavaScript」,因為它避免了版本控制。
let
宣告原則上,語言的新版本是一個清理它的機會,方法是移除過時的特色或變更特色運作的方式。這表示新的程式碼無法在較舊的語言實作中運作,而舊的程式碼也無法在新實作中運作。每一小段程式碼都連結到語言的特定版本。處理不同版本時,有兩種常見的方法。
首先,你可以採取「全有或全無」的方法,並要求如果程式碼庫想要使用新版本,就必須完全升級。Python 在從 Python 2 升級到 Python 3 時採取了這種方法。它的問題在於,一次將所有現有的程式碼庫進行移轉可能不可行,特別是當它很龐大的時候。此外,這種方法對網路來說並非選項,因為你永遠都會有舊程式碼,而且 JavaScript 引擎會自動更新。
其次,你可以允許程式碼庫包含多個版本的程式碼,方法是為程式碼加上版本標籤。在網路上,你可以透過專用的網際網路媒體類型標記 ECMAScript 6 程式碼。此類媒體類型可以透過 HTTP 標頭與檔案關聯
Content-Type: application/ecmascript;version=6
也可以透過 <script>
元素的 type
屬性關聯(其預設值為 text/javascript
)
<
script
type
=
"application/ecmascript;version=6"
>
···
</
script
>
這指定版本超出頻段,在實際內容外部。另一個選項是在內容內指定版本(頻段內)。例如,透過以下列開始檔案
use
version
6
;
兩種標記方式都有問題:頻段外版本易碎且容易遺失,頻段內版本會為程式碼增加雜訊。
更根本的問題是,允許每個程式碼庫有多個版本,實際上會將語言分叉成必須並行維護的子語言。這會造成問題
因此,版本控制是需要避免的事情,特別是對於 JavaScript 和網路。
但我們如何擺脫版本控制?透過永遠保持向下相容性。這表示我們必須放棄一些我們對清理 JavaScript 的野心:我們不能引入重大變更。保持向下相容性表示不移除功能,也不變更功能。此原則的口號是:「不要破壞網路」。
不過,我們可以新增新功能,並讓現有功能更強大。
因此,新引擎不需要版本,因為它們仍然可以執行所有舊程式碼。David Herman 將這種避免版本控制的方法稱為 One JavaScript (1JS) [1],因為它避免將 JavaScript 分割成不同的版本或模式。正如我們稍後將看到的,1JS 甚至會取消一些已經存在的分割,因為嚴格模式。
One JavaScript 並不表示您必須完全放棄清理語言。您不是清理現有功能,而是引入新的、乾淨的功能。一個範例是 let
,它宣告區塊範圍變數,而且是 var
的進階版本。不過,它並未取代 var
。它與 var
並存,作為較佳的選項。
有一天,甚至有可能移除不再有人使用的功能。ES6 的一些功能是透過調查網路上的 JavaScript 程式碼而設計的。兩個範例是
let
宣告難以新增至非嚴格模式,因為 let
在該模式中不是保留字。唯一看起來像是有效 ES5 程式碼的 let
變體是
let
[
x
]
=
arr
;
研究顯示,網路上沒有任何程式碼在非嚴格模式中以這種方式使用變數 let
。這使得 TC39 能夠將 let
加入非嚴格模式。稍後會在 本章節中說明 執行此動作的詳細資訊。
嚴格模式 在 ECMAScript 5 中引入,用於清理語言。它會在檔案或函式中將下列列第一行開啟
'use strict'
;
嚴格模式引入了三種中斷變更
with
陳述式被禁止。它讓使用者將任意物件加入變數範圍鏈,這會減慢執行速度,並讓使用者難以找出變數所指為何。implements interface let package private protected public static yield
ReferenceError
。在非嚴格模式中,這時會建立一個全域變數。TypeError
。在非嚴格模式中,它只會沒有作用。arguments
不再追蹤參數的目前值。this
在非方法函式中為 undefined
。在非嚴格模式中,它會指涉全域物件(window
),這表示如果您在沒有 new
的情況下呼叫建構函式,就會建立全域變數。嚴格模式是一個很好的例子,說明了版本控制為何棘手:儘管它啟用了更簡潔的 JavaScript 版本,但它的採用率仍然相對較低。主要原因是它會中斷一些現有程式碼,可能會減慢執行速度,而且將其新增到檔案(更不用說互動式命令列)很麻煩。我喜歡嚴格模式這個想法,但卻沒有經常使用它。
一個 JavaScript 表示我們無法放棄隨意模式:它將繼續存在(例如在 HTML 屬性中)。因此,我們無法在嚴格模式之上建構 ECMAScript 6,我們必須將其功能新增到嚴格模式和非嚴格模式(又稱隨意模式)中。否則,嚴格模式將會是語言的不同版本,我們將回到版本控制。不幸的是,兩個 ECMAScript 6 功能難以新增到隨意模式:let
宣告和區塊層級函式宣告。讓我們來探討原因以及如何新增它們。
let
宣告 let
讓您可以宣告區塊範圍變數。難以新增到隨意模式,因為 let
僅在嚴格模式中為保留字。也就是說,以下兩個陳述式為合法的 ES5 隨意程式碼
var
let
=
[];
let
[
x
]
=
'abc'
;
在嚴格 ECMAScript 6 中,您會在第 1 行收到例外,因為您將保留字 let
用作變數名稱。而第 2 行中的陳述式會被解釋為 let
變數宣告(使用解構)。
在隨意 ECMAScript 6 中,第一行不會導致例外,但第二行仍會被解釋為 let
宣告。在網路上使用識別碼 let
的這種方式非常罕見,因此 ES6 可以負擔得起這種解釋。撰寫 let
宣告的其他方式不會被誤認為隨意 ES5 語法
let
foo
=
123
;
let
{
x
,
y
}
=
computeCoordinates
();
ECMAScript 5 嚴格模式禁止在區塊中宣告函式。規範在草率模式中允許宣告函式,但未指定函式應如何運作。因此,各個 JavaScript 實作支援函式宣告,但處理方式不同。
ECMAScript 6 希望區塊中的函式宣告僅限於該區塊。這作為 ES5 嚴格模式的延伸是可以的,但會中斷一些草率的程式碼。因此,ES6 為瀏覽器提供「網頁舊版相容性語意」,讓區塊中的函式宣告存在於函式範圍中。
識別碼 yield
和 static
僅在 ES5 嚴格模式中保留。ECMAScript 6 使用特定於內容的語法規則,讓這些識別碼在草率模式中運作
yield
僅在產生器函式中為保留字。static
目前僅在類別字面中使用,類別字面會隱含嚴格(請見下方)。模組和類別的主體在 ECMAScript 6 中會隱含嚴格模式,不需要 'use strict'
標記。考量到我們的程式碼在未來幾乎都會存在於模組中,ECMAScript 6 實際上將整個語言提升為嚴格模式。
其他建構的主體(例如箭頭函式和產生器函式)也可以隱含嚴格。但考量到這些建構通常很小,在草率模式中使用它們會導致程式碼在兩種模式之間分散。類別和模組特別大,因此分散會比較不是問題。
One JavaScript 的缺點是無法修正現有的怪癖,特別是以下兩個。
首先,typeof null
應傳回字串 'null'
,而非 'object'
。TC39 嘗試修正它,但它中斷了現有的程式碼。另一方面,新增新類別運算元的結果是沒問題的,因為目前的 JavaScript 引擎偶爾會為宿主物件傳回自訂值。ECMAScript 6 的符號就是一個例子
> typeof Symbol.iterator
'symbol'
其次,全域物件 (瀏覽器中的 window
) 不應在變數的範圍鏈中。但現在要變更它也為時已晚。至少,在模組中不會在全域範圍內,而且 let
永遠不會建立全域物件的屬性,即使在全域範圍內使用也是如此。
ECMAScript 6 確實引入了幾個次要的重大變更 (您不太可能會遇到)。它們列在兩個附錄中
One JavaScript 表示讓 ECMAScript 6 完全向後相容。成功做到這一點真是太棒了。特別感謝的是,模組 (因此我們的大部分程式碼) 隱含地在嚴格模式中。
短期而言,將 ES6 結構新增到嚴格模式和隨意模式中,在撰寫語言規格和在引擎中實作時,會增加更多工作。長期而言,規格和引擎都受益於語言沒有分岔 (減少膨脹等)。程式設計師立即從 One JavaScript 中受益,因為它讓入門 ECMAScript 6 變得更容易。
[1] 原始的 1JS 提議 (警告:已過時):「ES6 不需要選擇加入」,作者為 David Herman。