初探ECMAScript 6

作者/洪坤德

前言 ECMAScript 6是國際JavaScript新版的標準,該標準的出現將對JavaScript帶來不同的定位,此次更新提供結構化與模組化的寫法,強化了語言本身的架 構;而更高語言撰寫約束,則是避免個模組之間產生衝突,雖然發展越來越接近一般的高階編程語言,但這也讓在Web Client端能夠使用的應用越來越強大與複雜。 ECMAScript 是一種腳本程式語言,由Ecma International(前身為歐洲計算機製造商協會)透過ECMA-262標準化。在ECMAScript尚未出現之前Microsoft與Netscape 的JavaScript技術各自為政,分別針對自家的瀏覽器提供不同的功能,因此各平台之間存在著相當大的差異。1997年6月ECMA發表了第一版ECMAScript標準後,各家的JavaScript才陸續依此進行發展,並分別就ECMAScript的標準實現與擴展其功能。此版本是自從2009年ECMAScript 5公佈至今第一次重大的更新,目前常見的瀏覽器包含Chrome、Firefox、Internet Explorer、Opera、Safari…等,皆已支援ECMAScript 5所規範的內容。 早期的JavaScript主要是用讓DOM與CSS能夠有些連結與互動,很少人會把它拿來寫大型的專案或者是複雜的應用,近年來由於Mobile 的興起及HTML 5 出現,再加上Google 開啟瀏覽器效能的競爭之路後,各平台逐漸重視JavaScript的效能。相對應的Client端應用也越來越複雜,既有的JavaScript陸續出現了對於部分功能支援不足的現象,為了因應大型或複雜應用程式的需求,Ecma International催生了ECMAScript 6。 本文就目前拍板的ECMAScript 6 草稿中,介紹些與之前不同的特點,不過目前各家瀏覽器只支援ECMAScript 6部分特點,因此無法靠單一瀏覽器進行測試,本文將利用了Firefox Nightly 34.0a1、Chrome 38.0.2114.2 m (64-bit) 或搭配Traceur呈現,ECMAScript 6於各瀏覽器的支援如《圖一》:

《圖一》
ECMAScript 6 特點 從EMCAScript 6所公布的草稿來看,大概可以條列為以下幾類,依序進行介紹:
  • 新的型別、修飾詞:包含了let、const、Rest、Spread、Symbol、Map、Set、Proxy、Promises…等。
  • 既有型別或物件的擴充:String、Number、Math、Object、Array 。
  • 簡便的寫法與用法: Default function parameter、For…of、Interactor、Generator、Destructuring。
  • 結構化的應用:Class、Module。
新的型別、修飾詞 (1)Let變數 var 是常用的變數宣告方式,以for loop為例,通常在該區塊內宣告的變數會希望是區域變數,不希望外部可以使用,但在EMCAScript 6之前是可能發生。這在程式的撰寫上經常會出現不易除錯的情形;但是EMCAScript 6之後將提供封閉區塊的區域變數宣告let,使用let宣告的變數將只能在該封閉區塊才允許讀取。

《圖二》
參考MDN的說明,let 也不允許同一個區塊內重複宣告,並且若再宣告前即使用則會引發Type Error,範例如 《圖三》:

《圖三》
(2)Const變數 Const是一個常數,只允許單次給值,不過Const只有在該區域內有用。由於靜態的限制,避免給值前使用。

《圖四》
(3)Rest變數、Spread修飾詞 Rest變數(…變數) 提供了如C# parms 如同的效果,避免相同作用但是引數不同方法同時存在。若將Spread (…)置於Array之前,則會將Array的內容依序擺放於function 的參數中,不須特別將其展開。

《圖五》
所以如果想合併兩個Array,就可以直接將兩個Array Spread後放在同一個Array裡面,不過若將兩個Array結合完後再拆解,有可能導致數值判斷為字串。

《圖六》
字串也具備相同的效果,Spread(…)會將字串拆成各置獨立的字元,傳入Function中,範例如下:

《圖七》
(4)Symbol 資料型別 Symbol 是ECMAScript 6提供的一種新的資料型態,最大的特點是每一個Symbol都不相等,具備獨一無二的值,因此可以用來作為確保物件或屬性間唯一而不衝突的用途;若要從全 域取得同一個key值所生成的Symbol物件,就必須要利用Symbol.for( key )去取得(若不存在則生成),相對的若要從全域的Symbol物件中取得其key值,則必須利用Symbol.For( sym ),用Symol函數生成的則會取不到。

《圖八》
另外Symbol還有提供其他的method,整理如《圖九》,不過目前尚無平台支援,因此無法測試。

《圖九》
(5)Map、Set、WeakMap、WeakSet 集合 Map 提供了類似Dictionary的機制,讓User可以指定key與Value的對應,而其功用有點像是Object一樣有key與Value的對應,不 過因為Object本身有部分內建的key所以不如Map好用,況且Object所存放的值皆為String。而WeakMap與Map類似,不過 WeakMap只允許object作為key,而且WeakMap對於key object的鏈結是較弱的,所以有可能會因為garbage collection而消失。

《圖十》
Set 則提供了存放唯一值物件,因此在Set裡面不會存在相同的值,不論是 Object、String、Number、Boolean、NaN、undefined或Symbol皆可以存放。WeakSet提供了Object的 集合,WeakSet裡的Object只會產生一次也是唯一的,與Set的差異在於WeakSet只允許存放Object,而且如同WeakMap一般, 所鏈結的object可能會因garbage collection而消失。

《圖十一》
(6)Proxy 型別 有點像是檢哨站,可以用來將所有需要額外處置的行為包裹起來,在該對象被使用之前必須先執行所包裹的內容。

《圖十二》
(7)Promises 型別 Promises是一個用來提供非同步執行的函式庫,Promises允許methods如同同步執行般的回傳值。為了定義callback的方法,Promises提供了then方法,用來指定resolve和reject的callback方法。

《圖十三》
不過then方法還可以進行串接,另外若要擷取then方法中所產生的例外則可以串接catch方法。

《圖十四》
既有型別或物件的擴充 (1) String的擴充 JavaScript內部對於字元的儲存使採用UTF-16,因此對於Unicode的部分字元支援通常不足,ECMAScript 6即針對這部分進行擴充,以下分別就細節進行說明: A. Unicode表示法 為了表示超過UTF-16所能表示的文字,JavaScript提供在Unicode編碼前加上”\u”的形式表示,但是單一字節所能表達的範圍只有”\u0000”~”\uffff”。

《圖十五》
B. CodePointAt、FromCodePoint 常見的是對於字元的誤判,由於Unicode的部分文字編碼大於0xFFFF,因此JavaScript會將其判斷成兩個字元。codePointAt這個方法主要就是用來判斷是否為Unicode的方式;fromCodePointy則是用來將Unicode轉為String。

《圖十六》
C. Repeat、Contains、StartsWith、EndsWith repeat提供了重複字串的功能,可以指定需重複的次數;contains則是提供字串的搜尋功能,判斷在該字串中是否具備指定的字串;startsWith則是用於判斷字串開頭是否為指定的字串,endsWith則相反,用於判斷結尾。

《圖十七》
D. Template Strings Template strings 使用反引號( ` )作為辨識,除了跟一般單引號( ’ )以及雙引號( ” )有相同的用法之外,還提供多行以及內嵌變數的功能,範例如下,惟內嵌變數必須用${ }包裝。

《圖十八》
(2) RegExp 為了因應String支援Unicode的功能,正規表達式亦支援使用”\u”處理大於\uFFFF的Unicode。

《圖十九》
另一個flag為修飾詞y (sticky)用以尋找相符合的部分,與修飾詞g不同的地方在於,g代表在剩餘的內容找得到即可,而y堅持必須要在剩下內容的第一個字元就必須符合。

《圖二十》
(3) Number的擴充 A. 二進制與八進制 ECMAScript 6針對二進制與八進制提供了新的表示法,分別是使用前綴詞0b與0o,以提供更完整的數字表示方式。

《圖二十一》
B. EPSILON、MIN/MAX_SAFE_INTEGER、isInteger… EPSILON、MIN/MAX_SAFE_INTEGER為Number所新提供的三個常數,EPSILON代表的是Number所能提供的最小數值;而MIN _SAFE_INTEGER及MAN_SAFE_INTEGER則代表分別代表JavaScript所能提供的最小及最大的正整數與負整數。(JavaScript是採用IEEE 754雙精度浮點格式,因此其可表示的整數分別為正負253 -1)

《圖二十二》
isInteger、isSafeInteger兩個為Number所提供的靜態方法,前者用來檢驗傳入的數值是否為整數,NaN、無限及其他都回傳false;後者則是檢驗是否為安全的整數(介於正負253 -1 之間)。另外,JavaScript中25與25.0儲存的方式相同,所以會被視為同一個值。

《圖二十三》
(4) Math的擴充 ECMAScript 6為了使Math函數能夠應付更多的數值需求,定義許多新的Method整理如《圖二十四》:

《圖二十四》
(5) Object的擴充 A. Assign、is object.assign主要是用在把可列舉的對象複製到目標對象之中:object.is則是用來比對兩者的值是否相等,若同樣為+0、-0、undefined、NaN…則回傳True。

《圖二十五》
B. __proto__、getOwnProperty、setPrototypeOf __proto__為Object內用來存放內部prototype,不過在ECMAScript 6的規畫當中,並不允許直接存取,必須透過getOwnProperty及setPrototypeOf進行存取,藉此實作各種常用的物件。

《圖二十六》
(6) Array的擴充 A. from、of ECMAScript 6提供了Array.from這個方法把,其他種類的集合轉變成Array,使其能夠更簡易的使用Array所提供的擴充功能;另外亦支援帶入第二個引數,可以分別針對Collection的內容進行處理。而Array.of則是為了補足Array針對單一數值無法轉換的不足。

《圖二十七》
B. copyWithin、fill copyWithin(target, start, end = this.length)提供指定目前array的目標、起點及終點三個引數,複製成新的Array;其中end預設為原始array的長度,因此end代表為到該index前為止。而一樣是帶入三個引數的fill,則是在target帶入新值。

《圖二十八》
C. find、findIndex find及findIndex都是在Array中分別尋找第一個符合條件的對象,差別在於find是傳回值,findIndex是傳回該對象位置;只是因為find及findIndex都是使用callback,所以必須要在callback中撰寫搜尋條件。

《圖二十九》
D. keys、values、entries ECMAScript 6針對Array提供了三個ArrayIterator,藉由next()可依序將內容讀出,當也可以使用for…of 的方式;其中keys是對應Array的Index集合,values則是對應值,entries則包含為成對的Index 跟value。值得留意的是,ArrayIterator索回傳的都是object,回傳的值放在value之中,done則記錄著是否已經終止的Boolean值。

《圖三十》
簡便的函數寫法與用法 (1) Destructuring Destructuring是ECMAScript 6所提供的給值的方式,一般在賦予變數值得方式為一對一宣告,而在ECMAScript 6的規範之中,允許等號的兩邊按照相同的模式進行給值。如果無法對應,則給予undefined。

《圖三十一》
(2) Tail Calls 模式 在撰寫遞迴時,每次的遞迴呼叫都會導致新的Stack frame產生,導致整個呼叫所編譯出來的Call Stack過大,而Tail Call算是一種演算法,指的是在該子程式的尾端再進行遞迴呼叫,其中當然要將所要回傳的值帶入,目的是希望藉此不必再額外產生新的Stack frame;不過必非所有的程式語言都有支援這樣特性,所以過大的數值通常都導致Stack overflow,而在未來的ECMAScript 6將會提供這樣的特性。範例程式如下,雖然目前尚未有平台可以進行測試。

《圖三十二》
(3) Arrow Function 表示法 Arrows是一種function的縮寫方式,有點像是C#的利用=>作為新的語法,可以用在expression以及句子的主體之中,不過跟一般function不同的是,關鍵字this在Arrow function中所代表的是定義function所在的對象,而非被觸發時的對象,因此要特別小心。

《圖三十三》
(4)Default Function Parameter 設定 提供Function 變數的預設值設定,與C#相似。可以提供較大的彈性使用。

《圖三十四》
(5) For Of Loops用法、Iterator協定 ECMAScript 6提供了將所有Array中的元素取出的功能,有別於in是取得Index。不過特別的是ECMAScript 6提供了一種名為Iterator的協定,只要具備這個協定的集合接能夠使用For…of將數值讀出,而Iterator有另外提供了一個next()方法,該方法會回傳一個包含value及done兩個屬性的物件;其中value代表的該對象的值,而done代表著該位置是否為終點。

《圖三十五》
(6)Generator ECMAScript 6利用function* and yield 可簡易的創造出Generator,Generator內部可以撰寫特定的規則,當滿足該規則時,引發特定事件。不過使用Generator所回傳的是Iterator,因此必須使用next()執行。

《圖三十六》
結構化的應用 (1) Classes 類 ECMAScript 6提供Class作為定義類別使用,Class之間可以利用extends進行繼承,其中所繼承的父類別可以用super call的方式初始化並取得其執行個體。類別裡面可以有constructor,而關鍵字this在此時則代表著該Class本身。

《圖三十七》
(2)Modules 模組塊 ECMAScript 6提供了modules支援,目的是為了取代常用的JavaScript module loaders ( CommonJS及AMD )。利用export和import 分別將不同js檔中的區塊進行輸出或加載,方便各js之間使用且避免彼此之間的名稱發生衝突。

《圖三十八》
ECMAScript 6影響 從草稿的內容看來,ECMAScript 6的出現對開發者來說有幾點影響: 1.更簡易的開發 為了更簡易的達成Client端所需的複雜應用,新的規格訂定了新的變數( let 、const、rest …)、型別( symbol、map/set、proxy…)及擴充功能( string、number、object、array…)等,更加完善的功能使得應用本身可以更多元、更便利,不在需要利用自行撰寫的方法或物件模擬特 定行為。對於既有開發者而言,ECMAScript 6的出現將加速應用的開發,再加上該版本向下相容,在使用既有的功能不需有太多的調整,因此無論是更新或維護既有的應用並不會產生太大的影響;而對新的開 發者來說,由於ECMAScript 6越來越偏向一般的高級編程語言,因此其入門門檻相對的也比較低。 2.較嚴謹的寫法 對於既有的開發者而言,ECMAScript 6調整了變數及Scope的使用( let、const…),使得該語言的變數使用越來越嚴謹,因應不同的目的,所需宣告的變數將不再相同,既有的開發習慣勢必得調整,不過好處是不同的應用 模組間的變數或方法不會再彼此影響,因此在大型專案的運作之下,平行開發的難度就變低了。 3.結構化的用法 另 外一個重大的變革是新的Class與Module的使用,ECMAScript 6的使用越來越組織及結構化,Class同時提供了繼承的機制,使子類別可以直接利用super及搭配constructor的使用參考其父類別;對於開 發者而言,必須習慣新的組織及結構化的方式,避免繼續使用利用變數及物件的變通方式。 4.流程控制 Generator/Iterator為函數提供了不同的進入點,而Promise更是針對非同步流程控制的部分提供了強大的解法,對於開發者來說這無疑是一大福音,對於整題 流程的掌握將越來越多元及完整;可惜的是目前尚未能實際了解各瀏覽器的支援狀況,因此開發者在開發時可能必須要同時考量各家瀏覽器對於流程的控制方式的差 異。
結論 ECMAScript 6的正式版本雖然要等到今年底或明年中才會公佈,但以目前草稿的內容看來,新的型別、物件甚至結構與模組化的出現,讓未來Client端的應用越來越強大,在mobile及Web掛帥的時代,使用者對於Web端的應用與依賴已經越來越高,屆時所產生的影響絕對不只筆者所討論到的部分;雖然以目前瀏覽器支援的情況看來,ECMAScript 6距離完整實作還有一段距離,不過ECMAScript 7已經開始規劃,許多ECMAScript 6來不及加入的實作早已納入討論,因此無論是既有或新加入的開發者,絕不能等到瀏覽器完全支援後才開始熟悉,因為在ECMAScript 6拍板的同時,各家瀏覽器業者絕對會快速的因應這一波變革,如何在瀏覽器支援的同時甚至更早提出新的應用,將是Web Client開發者最重大的課題。(本文轉載自2014/9/6發表之RUN!PC文章)
參考資料 1. ECMAScript Web Site 2. ECMAScript 6 Features 3. ECMAScript 6 Compatibility Table 4. ECMAScript 6 Specification Drafts 5. ECMAScript 6 support in Mozilla 6. Mozilla Developer Network