就歷史角度來說,先有JavaScript語言,才有了ECMA-262規範,或稱ECMAScript,而有了規範之後,JavaScript角色就成了實作語言之一,實際上,JavaScript是個商標,原屬於Sun,後來Oracle收購Sun之後,JavaScript商標權目前由Oracle擁有,為了迴避商標問題,也有不少人開始直接以ECMAScript,來指稱JavaScript這門實作,特別是在ECMAScript 2015之後,多半用ES6來表示,以彰顯這門語言的重大變更。

ES6非常成功,開發者應對當中特性有不少認識,實際上,從ES7起,ECMAScript採取頻繁、持續每年釋出新版本的方式,而新版包含當年已完成的新特性,常青瀏覽器與Node.js都會緊跟,針對新特性完成實作。若要認識這些特性以善加利用,網路搜尋而來的文件常過於混亂,甚至是過時資訊,〈ECMA-262〉又過於冗長,因此,最快方式的是查閱TC39的〈ECMAScript proposals〉。

ECMA之下,有許多技術委員會(Technical Committees)與任務群組(Task Groups),其中的TC39,就是負責ECMAScript規範的技術委員會,語言的提案處理程序在〈The TC39 Process〉中,可以看到五個階段,而只有TC39成員可以建立階段0稻草人(Strawperson),此時純粹就是構想,未正式在TC39進行討論。

實務開發者比較感興趣的,應是進入階段3的提案,這時的提案,幾乎確定會是未來正式規範的一部份,只待規範的一些文件完成、審校與核可,各大常青瀏覽器與Node.js,通常在此時也提供實作。

提案可能會在階段3停留許久,例如,作為動態載入模組的import()函式,就是一個例子(然而目前瀏覽器確實已經支援),只有在提案來到階段4時,才會成為下個版本ES正式規範的一部份。如果想確認特性真正是出現在哪個版本,我們可以查閱〈Finished Proposals〉,看看當中的預計發布年份。

例如,對於先前專欄〈ES6後的規則表示式〉以及〈ES9非同步迭代〉中談到的新特性,〈Finished Proposals〉的預計發布年份都是2018,也就是出現在ES9中;另外,發布於2017年ES8的async/await等,都是ES6之後值得留意、環境允許下加以使用的重大特性;而2016年ES7其實還新增了個指數運算子**,在一些數學運算場合的使用上,還頗為方便。

就撰寫本文的時間點而言,下個ECMAScript版本會是ES10,從〈Finished Proposals〉預計發布年份為2019的相關特性,我們可以看到,多半是API的增強,其中有個語法層面的catch變更,若捕捉後不需要操作error實例,catch將不強制宣告名稱了。

目前階段3中的類別提案

2019年Google I/O大會中,V8團隊在〈What’s new in JavaScript〉(https://bit.ly/30WnStc),談到不少ECMAScript未來新特性,多半是處於階段4或3的提案(少數是階段2的特性),若想跟上ECMAScript提案最近狀態,不妨看看。

特別吸引我的地方在於,一些類別語法的增強,JavaScript自始至終就是基於原型的物件導向語言,對於熟悉基於類別的物件導向語言開發者而言,在面對JavaScript中物件的特性、方法查找與新增等處理時,往往難以掌握,因此,ES6加入類別語法,雖不改變ECMAScript基於原型的特性,但確實為熟悉基於類別的語言開發者,提供操作原型的捷徑。

不過想當然爾,對JavaScript來說,加入類別語法是個冒險的舉動,所以,ES6在新增類別語法時是極為保守的。例如,必須在constructor中初始特性值,這就造成:子類別定要在constructor中,以super呼叫父constructor來初始父類中的特性;不提供私有特性或方法,不能有私有的get、set函式;只能定義static方法、但不能定義static特性等。

幸而ECMAScript在類別語法的導入算是成功的,目前處於階段3的提案中,我們可見到大膽增加不少針對類別語法的增強,像是:可以在類別本體中建立特性並初始當中的值,若要定義私有特性,只需要在特性名稱前加上#,例如:

class Foo {
#value = 0;
get value() { return this.#value; }

不單是特性,方法或get、set函式,也可以在名稱前加上#而成為私有,對於真的打算在類別中實現私有性的開發者而言,這應該是個福音。若想要建立static特性,可以在類別本體撰寫static value = 0這類語法;static特性或方法若要成為私有,同樣地在名稱前加上#就可以了。

階段3的其他提案

除了類別語法增強,階段3其他提案值得留意的部分,還有全域物件的取得、新的基本型態bigint,以及數字分隔符。數字分隔符這個看似不起眼的特性,但對於「1000000000000」,可寫為1_000_000_000_000,絕對有助於閱讀程式碼。

在不同環境中,想取得全域物件,往往有不同的方式,例如,在瀏覽器中使用window,在Node.js中使用global,有些情況下需用Function('return this')()。如果想提供一個getGlobal函式(〈What’s new in JavaScript〉中已談到),可透過該函式來取得全域物件,卻難以有完美的方案,而現在階段3有個globalThis,將作為未來取得全域物件的關鍵字。

階段3為ECMAScript增加了新基本型態bigint,這提醒了許多ECMAScript開發者常忽略的事實:ECMAScript中的數字,都是IEEE 754標準64位元浮點數,若只用來表示整數,安全的整數範圍,由正到負為9007199254740991至-9007199254740991(在ES6中,可以用Number上的MAX_SAFE_INTEGER與MIN_SAFE_INTEGER來取得),但處理到9007199254740992時並不安全,因為,若寫下9007199254740993,結果還是代表著9007199254740992。

這代表著,在安全整數範圍外的整數,都是不可信任的,若要處理範圍外的整數,須依賴第三方程式庫。在目前階段3有個BigInt提案,撰寫整數時若最後加上個n,表示會是個bigint基本型態,因此,若處理9007199254740992n與9007199254740993n或更大的bigint時,它們都是安全整數了。

現有與數字相關的運算子,都可以套用在bigint,不過bigint有著嚴格的型態要求,只有同為bigint的兩數才能進行運算,因為1+1n這類的運算會引發TypeError。如果想將number轉為bigint,必須使用BigInt函式;若要將bigint轉為number,可以使用Number函式,當然,要轉回number時需要注意,是否位於安全整數範圍之內。

與現有API結合使用時,因為這些API的內部實現中,多半會有與number做運算的流程,因而呼叫後可能會產生TypeError,例如Math.round(1n)就會發生錯誤,所以,未來這類API,想必要有針對bigint的版本。

不是只有ES6

ES6確實是個重大變更,然而,自2015年發布以來,也過了好多年,或許相對來說,瀏覽器環境難以控制,因此,後續ES7到ES9的特性,較少為開發者著墨,實在很可惜。

確實地,開發要務實,相容性是必須考量,不過,每次打算運用新特性時,就祭出相容性這面大旗又太過保守。因此,我們仍應設法認識新特性,在相對容易受控的場合,或者有工具或修補(polyfill)可以協助轉換處理相容性時,若能善加利用新特性,對於開發而言,會有莫大的助益。

專欄作者

熱門新聞

Advertisement