開發者多半討厭語言約束,特別是對新手而言,語言約束總是令他們感到沮喪,為了讓開發者盡可能控制一切,幾近無約束的JavaScript誕生於1995年5月,這被視為玩具的語言鹹魚翻身之後,因為沒有太多語法約束,開發者可盡情地在上頭創造想要的元素,這其中也創造了不少約束開發者的元素,從ECMAScript 5到ECMAScript 6的演進,就能夠瞭解這十幾年來,開發上必要的語言約束有哪些。
JavaScript在1996年進行標準化,第一個標準化版本ECMA-262於1997年採納,因為Java名稱上具有商標問題,ECMA-262採用了ECMAScript作為語言名稱,JavaScript此後成為了ECMA-262標準的實作語言,現在的瀏覽器,幾乎都能支援1999年發布的ECMA-262第三版以上的實作,又稱ECMAScript 3,對應的實作為JavaScript 1.5。
由於一些政治因素,ECMAScript 4發展意見發生分岐,原有規範中的一小部份是就現有功能作了改進,被發布為ECMAScript 3.1。後來ECMAScript 4被中止,ECMAScript 3.1被改名ECMAScript 5,並於2009年發布,後續2011年又發布了ECMAscript 5.1。
ECMAScript 5的主要特點之一是可宣告嚴格模式(Strict mode),透過在程式碼中加入'use strict'字串,一些過去被認為不良實踐的語法就會無法使用,嚴格模式下有許多規範,是關於識別字名稱使用上的限制,像是沒有使用var宣告的變數無法使用,不能使用with,不能有重複的參數或特性名稱,不能與保留字或既存的識別字名稱衝突(像是arguments),eval函式評估出來的變數不能在外部使用等,很顯然地,這反映了過去語言在範疇(Scope)與名稱空間(Namespace)管理上過於鬆散,因而必須加強範疇與名稱空間管理上的約束。
JavaScript中this的實際對象,可以依呼叫者(caller)自動變換,這雖是很強大的彈性,但也經常難以掌握。
像是ECMAScript 5之前,如果this沒有實際對象,也就是this實際上是null或undefined時,會自動轉換為全域物件,這又會造成許多誤判或誤用的問題,嚴格模式下禁止了這個行為,實際上,為了讓開發者能明確掌握this,函式新增了bind函式,可以讓你固定this的對象傳回新函式,讓this不會隨著呼叫者自動變換。在這之前,開發者為了要能更精確掌握this實際對象,避免不清不楚下發生問題,亦會明確地在使用call或apply函式時,指定this的對象。
針對物件個體化的約束
JavaScript中,開發者幾乎可以控制物件的一切。你可以從一個幾乎完全身無分物的物件開始,根據需求賦予它必要的能力,你也可以將物件的能力剝奪到幾近一無所有,這種基於原型基礎的語言,有利於快速堆砌功能,這也是後來JavaScript鹹魚翻身之後,生態圈得以迅速發展的原因之一。
然而,就如我先前專欄〈類別與原型的物件管理學〉中談過,這種特性「容易造成維護上的混亂,在應用於工程性偏重的場合,往往得採取某種慣例來約束物件行為……避免開發者直接修改物件的行為與結構」。
ECMAScript 5中搭配嚴格模式,可以使用Object.preventExtensions函式來阻止對物件直接進行擴充,進一步地,還可以定義特性描述器(Property descriptor),搭配Object.defineProperty、Object.defineProperties函式,來指定特性是否可修改、刪除或列舉,為了有更明確的語義,還有Object.seal函式可以對物件彌封,而被彌封的物件不能擴充或刪除物件上的特性,亦有Object.freeze函式可用來凍結物件,讓物件成為唯讀物件。
物件個體化能力,在專案剛啟始、需求變化快的場合很有用,可以快速堆砌功能,然而當專案逐漸邁入維護份量加重的場合時,嚴格模式下對物件個體化的約束,讓JavaScript更實質地約束物件行為。
語言的約束並非不好,實際上慣例本身也是約束,若專案一開始沒有慣例約束,後來想貿然加上嚴格模式或直接對物件個體化做出限制,也只會驟然面對龐大的錯誤訊息,不可能直接享用到ECMAScript 5可選用的嚴格模式與物件個體化限制之好處。
ECMAScript 6區塊範疇、類別與模組
被中止的ECMAScript 4中一部份,後來演變為ECMAScript 6,於2013年凍結了ECMAScript 6草案停止增加新功能,預計於2015年中發布正式版本,目前Google的V8實現了部份特性。
例如,可使用Node.js 0.11版來體驗。在語言的約束上,ECMAScript 6增加了let宣告,與var不同的是,let宣告的變數範疇只限宣告當時所在區塊(Block),也不可以重複宣告同一變數,也不會如同var宣告的變數一樣具有提升(Hoisting)的特性,這比較符合多數程式語言中變數的特性。
JavaScript是基於原型(Prototype-based)的語言,沒有類別的概念,然而,ECMAScript 6中增加了class、constructor、extends等語法,可支援基於類別(Class-based)的封裝、繼承,乍看似乎基於原型、基於類別兩個系統相互衝突,其實ECMAScript 6的類別語法極為精簡,主要目的讓過去類別風格封裝、繼承模擬的各種作法,能有個標準方式。
拆解ECMAScript 6的類別語法後會發現,可對照現階段使用function模擬類別的常用手法,某些程度上,ECMAScript 6中規範的類別就像語法蜜糖。
對於基於類別的語言來說,類別規範了實例的行為與結構,我在RubyConf 2014的〈Understanding Typing. Understanding Ruby.〉中談過,即使是支援物件個體化的Ruby,也可以經由觀察與重構,讓必要的行為座落於適當的module與類別之中,而這概念在JavaScript中也是適用,如果過去曾經使用過慣例上function模擬類別的作法,直接使用ECMAScript 6支援class等的語法,基本上不困難,語義明確且程式碼也會簡潔許多。
在JavaScript中,名稱空間管理與模組載入是個很大的問題,過去花了許多功夫在各種方案上,甚至有了專門處理這類問題的框架,像是RequireJS。
ECMAScript 6為了解決這類問題而規範了模組特性,讓文件本身就是一個模組,可使用export來匯出名稱,使用import來匯入名稱並創造名稱空間。這過去可能自行以慣例或運用框架來約束,ECMAScript 6讓這類議題有了標準作法,且語義明確。
語言約束背後是怎樣的慣例?
從ECMAScript標準化到ECMAScript 6,是有趣的過程,如Brendan Eich在為《Effective JavaScript》寫序時提到,在一堆挑戰性高的需求及發瘋般的時程下,他能做的,就是讓JavaScript成為可從頭塑造的語言,開發者能依自己的需求,來為JavaScript上patch,多年來,眾多開發者確實也為JavaScript,形塑了它應有的樣貌,或說是在JavaScript累積許多最佳實踐。
Brendan Eich也談到:「所有語言幾乎都是受限地實行常見的最佳實踐」,JavaScript因緣聚會,在幾近無約束的情況下,先累積了諸多開發經驗與慣例,接著回饋至語言本身成為可選的約束,從另一個角度看過來就是,沒有語言會為了整開發者而加入語法約束,語言若在一開始建立好相關的約束,應有其背後的原因。
這也就是,不要只從語法或關鍵字上來學習語言的原因,應思考相關語法或約束背後,是否為了解決哪些問題,或是否曾借鏡其他語言的使用慣例。
實際上ECMAScript 6尚未正式發布,而真正各平臺若要實作採行,也需要相當時間。
在這之前,可從ECMAScript 6的規範中,先吸取其語法約束背後之精神,就當作目前可用的JavaScript實作平臺上已有這類約束一般,將來若真要採用ECMAScript 6規範的語法,就不至面臨太大問題。否則在毫無約束之下,即使只是加上個'use strict',也足夠讓現有程式一片混亂了!
專欄作者
熱門新聞
2024-12-27
2024-12-24
2024-12-22
2024-11-29
2024-12-20