歡迎光臨
每天分享高質量文章

使用ES6進行開發的思考

作者:百度EFE – otakustay

網址:http://efe.baidu.com/blog/es6-develop-overview/?qq-pf-to=pcqq.c2c

點選“閱讀原文”可檢視本文網頁版

ECMAScript6已經於近日進入了RC階段,而早在其處於社群討論時,我就開始一直在嘗試使用ES6進行開發的方案。在Babel推出後,基於ES6的開發也有了具體可執行的解決方案,無論是Build還是Debug都能得到很好的支援。

而在有了充足的環境、工具之後,我們面臨的是對ES6眾多新特性的選擇和分析,以便選取一個最佳的子集,讓我們可以享受ES6帶來的便利(減少程式碼量、提高可讀性等)的同時,也可以順利執行於當前以ES3-ES5為主的瀏覽器環境中。

經過分析後,本文試圖對ES6各個特性得出是否適合應用的初步結論,並一一解釋其使用場景。ES6的特性串列選自es6features。

  • ★★★ 推薦使用
  • ★★ 有考慮地使用
  • ★ 慎重地使用
  • ☆ 不使用

特性 推薦程度
arrows ★★★
classes ★★★
enhanced object literals ★★★
template strings ★★★
destructuring ★★
default + rest + spread ★★★
let + const ★★★
iterators + for..of ★★
generators
unicode
modules ★★
module loaders
map + set + weakmap + weakset ★★
proxies
symbols
subclassable built-ins
promises ★★★
math + number + string + array + object APIs ★★★
binary and octal literals
reflect api
tail calls ★★

接下來我們以上特性挨個進行介紹。需要關註一點:如果你不想使用shim庫(如Babel的browser-polyfill.js和generatorsRuntime.js)或者想使用盡可能少的helper(Babel的externalHelpers配置),那麼需要按你的需求進一步縮減可使用的ES6特性,如Map、Set這些就不應該使用。

語法增強類

Arrow function

Arrow functions是ES6在語法上提供的一個很好的特性,其特點有:

  • 語法更為簡潔了。
  • 文法上的固定this物件。

我們鼓勵在可用的場景下使用Arrow functions,並以此代替原有的function關鍵字。

當然Arrow functions並不是全能的,在一些特別的場景下並不十分適用,最為典型的是Arrow functions無法提供函式名稱,因此做遞迴並不方便。雖然可以使用Y combinator來實現函式式的遞迴,但其可讀性會有比較大的損失。

配合後文會提到的物件字面量增強,現在我們定義方法/函式會有多種方式,建議執行以下規範:

  • 所有的Arrow functions的引數均使用括號()包裹,即便只有一個引數:

// Good

let foo = (x) => x + 1;

// Bad

let foo = x => x + 1;

  • 定義函式儘量使用Arrow functions,而不是function關鍵字:

// Good

let foo = {

bar() {

// code

}

};

// Bad

let foo = {

bar: () => {

// code

}

};

// Bad

let foo = {

bar: function () {

// code

}

};

除非當前場景不合適使用Arrow functions,如函式運算式需要自遞迴、需要執行時可變的this物件等。

  • 對於物件、類中的方法,使用增強的物件字面量:

// Good

let foo = {

bar() {

// code

}

};

// Bad

let foo = {

bar: () => {

// code

}

};

// Bad

let foo = {

bar: function () {

// code

}

};

增強的物件字面量

物件字面量的增強主要體現在3個方面:

可在物件中直接定義方法

let foo = {

bar() {

// code

}

};

我們推薦使用這種方式定義方法。

可使用透過計算得出的鍵值

let MY_KEY = ‘bar’;

let foo = {

[MY_KEY + ‘Hash’]: 123

};

我們推薦在需要的時候使用計算得出的鍵值,以便在一個陳述句中完成整個物件的宣告。

與當前Scope中同名變數的簡寫

let bar = ‘bar’;

let foo = {

bar // 相當於bar: bar

};

我們並不推薦這樣的用法,這對可讀性並沒有什麼幫助。

模板字串

模板字串的主要作用有2個:

多行字串

let html =

`

Hello World

`

從上面的程式碼中可以看出,實際使用多行字串時,對齊是個比較麻煩的事。如果let html這一行本身又有縮排,那麼會讓程式碼更為難受一些。

因此我們不推薦使用多行字串,必要時還是可以使用陣列和join(”)配合,而生成HTML的場景我們應該儘量使用模板引擎。

字串變數替換

let message = `Hello ${name}, it’s ${time} now`;

這是一個非常方便的功能,我們鼓勵使用。但需要註意這些變數並不會被HTML轉義,所以在需要HTML轉義的場景,還是乖乖使用模板引擎或者其它的模板函式。

解構

解構(原諒我沒什麼好的翻譯)是個比較複雜的語法,比如:

let [foo, bar] = [1, 2];

let {id, name, children} = getTreeRoot();

還可以有更複雜的,具體可以參考MDN的檔案。

對於這樣一個複雜且多變的語法,我們要有選擇地使用,建議遵循以下原則:

不要一次透過解構定義過多的變數,建議不要超過5個。

謹慎在解構中使用“剩餘”功能,即let [foo, bar, …rest] = getValue()這種方式。

不要在物件解構中使用過深層級,建議不要超過2層。

函式引數增強

ES6為函式引數提供了預設值、剩餘引數等功能,同時在呼叫函式時允許將陣列展開為引數,如:

var foo = (x = 1) => x + 1;

foo(); // 2

var extend = (source, …args) => {

for (let target in args) {

for (let name in Object.keys(target) {

if (!source.hasOwnProperty(name) {

source[name] = target[name];

}

}

}

};

var extensions = [

{name: ‘Zhang’},

{age: 17},

{work: ‘hard’}

];

extend({}, …extensions);

我們鼓勵使用這些特性讓函式的宣告和呼叫變得更為簡潔,但有一些細節需要註意:

  • 在使用預設引數時,如果引數預設值是固定且不會修改的,建議使用一個常量來作為預設值,避免每一次生成的開銷。
  • 不要對arguments物件使用展開運算,這不是一個陣列。

關鍵字類

let和const

這是2個用來定義變數的關鍵字,眾所周知的,let表示塊作用域的變數,而const表示常量。

需要註意的是,const僅表示這個變數不能被再將賦值,但並不表示變數是物件、陣列時其內容不能改變。如果需要一個不能改變內容的物件、陣列,使用Object.freeze方法定義一個真正的常量:

const DEFAULT_OPTIONS = Object.freeze({id: 0, name: ‘unknown’});

不過如果你在程式中能控制不修改物件的話,這並不具備什麼意義,Object.freeze是否會引起執行引擎的進一步最佳化也尚未得到證實。

我們推薦使用let全面替代var。同時建議僅在邏輯上是常量的情況下使用const,不要任何不會被二次賦值的場景均使用const。

迭代器和for..of

迭代器是個好東西,至少我們可以很簡單地遍歷陣列了:

for (let item in array) {

// code

}

但是迭代器本身存在一些細微的缺點:

  • 效能稍微差了一些,對於陣列來說大致與Array.prototype.forEach相當,比不過原生的for迴圈。
  • 不能在迴圈體中得到索引i的值,因此如果需要索引則只能用原生的for迴圈。
  • 判斷一個物件是否可迭代比較煩人,沒有原生方法提供,需要自行使用typeof o[Symbol.iterator] === ‘function’判斷。
  • 對於迭代器,我們鼓勵使用並代替原生for迴圈,且推薦關註以下原則:
  • 對於僅一個陳述句的迴圈操作,建議使用forEach方法,配合Arrow functions可非常簡單地在一行寫下迴圈邏輯。
  • 對於多個陳述句的迴圈操作,建議使用for..of迴圈。
  • 對於迴圈的場景,需要註意非陣列但可迭代的物件,如Map和Set等,因此除arguments這類物件外,均建議直接判斷是否可迭代,而不是length屬性。

生成器

生成器(Generators)也是一個比較複雜的功能,具體可以參考MDN的檔案。

對於生成器,我的建議是非常謹慎地使用,理由如下:

  • 生成器不是用來寫非同步的,雖然他確實有這樣一個效果,但這僅僅是一種Hack。非同步在未來一定是屬於async和await這兩個關鍵字的,但太多人眼裡生成器就是寫非同步用的,這會導致濫用。
  • 生成器經過Babel轉換後生成的程式碼較多,同時還需要generatorsRuntime庫的支援,成本較高。
  • 我們實際寫應用的大部分場景下暫時用不到。

生成器最典型的應用可以參考C#的LINQ獲取一些經驗,將對一個陣列的多次操作合併為一個迴圈是其最大的貢獻。

模組和模組載入器

ES6終於在語言層面上定義了模組的語法,但這並不代表我們現在可以使用ES6的模組,因為實際在ES6定稿的時候,它把模組載入器的規範給移除了。因此我們現在有的僅僅是一個模組的import和export語法,但具體如“模組名如何對應到URL”、“如何非同步/同步載入模組”、“如何按需載入模組”等這些均沒有明確的定義。

因此,在模組這一塊,我們的建議是使用標準語法書寫模組,但使用AMD作為執行時模組解決方案,其特點有:

  • 保持使用import和export進行模組的引入和定義,可以安全地使用命名export和預設export。
  • 在使用Babel轉換時,配置modules: ‘amd’轉換為AMD的模組定義。
  • 假定模組的URL解析是AMD的標準,import對應的模組名均以AMD標準書寫。
  • 不要依賴SystemJS這樣的ES6模組載入器。

這雖然很可能導致真正模組載入器規範定型後,我們的import模組路徑是不規範的。但出於ES6的模組不配合HTTP/2簡直沒法完的考慮,AMD一定很長一段時間內持續存在,我們的應用基本上都是等不到HTTP/2實際可用的日子的,所以無需擔心。

型別增強類

Unicode支援

這個東西基本沒什麼影響,我們很少遇到這些情況且已經習慣了這些情況,所以可以認為這個特性不存在而繼續開發。

Map和Set

兩個非常有用的型別,但對不少開發者來說,會困惑於其跟普通物件的區別,畢竟我們已經拿普通物件當Map和Set玩了這麼多年了,也很少自己寫一個型別出來。

對於此,我們的建議是:

  • 當你的元素或者鍵值有可能不是字串時,無條件地使用Map和Set。
  • 有移除操作的需求時,使用Map和Set。
  • 當僅需要一個不可重覆的集合時,使用Set優先於普通物件,而不要使用{foo: true}這樣的物件。
  • 當需要遍歷功能時,使用Map和Set,因為其可以簡單地使用for..of進行遍歷。

因此,事實上僅有一種情況我們會使用普通的物件,即使用普通物件來表達一個僅有增量Map,且這個Map的鍵值是字串。

另外,WeakMap和WeakSet是沒有辦法模擬實現的,因此不要使用。

Proxy

這不是一個可以模擬實現的功能,沒法用,因此不要使用Proxy。

Symbol

Symbol最簡單的解釋是“可用於鍵值的物件”,最大的用處可能就是用來定義一些私有屬性了。

我們建議謹慎使用Symbol,如果你使用它來定義私有屬性,那麼請保持整個專案內是一致的,不要混用Symbol和閉包定義私有屬性等手段。

可繼承的內建型別

按照ES6的規範,內建型別如Array、Function、Date等都是可以繼承且沒有什麼坑的。但是我們的程式碼要跑在ES3-5的環境下,顯然這一特性是不能享受的。

Promise

這個真沒什麼好說的,即便不是ES6,我們也已經滿地用著Promise了。

建議所有非同步均使用Promise實現,以便在未來享受async和await關鍵字帶來的便攜性。

另外,雖然Babel可以轉換async和await的程式碼,但不建議使用,因為轉換出來的程式碼比較繁瑣,且依賴於generatorsRuntime。

各內建型別的方法增強

如Array.from、String.prototype.repeat等,這些方法都可以透過shim庫支援,因此放心使用即可。

二進位制和八進位制數字字面量

這個特性基本上是留給演演算法一族用的,因此我們的建議是除非數字本身在二/八進位制下才有含義,否則不要使用。

反射API

Reflect物件是ES6提供的反射物件,但其實沒有什麼方法是必要的。

其中的delete(name)和has(name)方法相當於delete和in運運算元,而defineProperty等在Object上本身就有一套了,因此不建議使用該物件。

尾遞迴

當作不存在就好了……

贊(0)

分享創造快樂