你可能已經據說過 ECMAScript 6
(簡稱 ES6)了。ES6 是 Javascript 的下一個版本,它有不少很棒的新特性。這些特性複雜程度各不相同,但對於簡單的腳本和複雜的應用都頗有用。在本文中,咱們將討論一些精心挑選的 ES6 特性
,這些特性能夠用於你平常的 Javascript 編碼中。html
請注意,當前瀏覽器已經全面展開對這些 ES6 新特性的支持,儘管目前的支持程度還有所差別。若是你須要支持一些缺乏不少 ES6 特性的舊版瀏覽器,我將介紹一些當前能夠幫助你開始使用 ES6 的解決方案。node
文中大多數代碼示例都帶有「運行代碼」連接,你能夠查看代碼並運行它。git
你習慣於用 var
聲明變量。如今你也能夠用 let
了。二者微妙的差異在於做用域。var
聲明的變量做用域爲包圍它的函數,而 let
聲明的變量做用域僅在它所在的塊中。 es6
if(true) { let x = 1; } console.log(x); // undefined
這樣使得代碼更加乾淨,減小滯留的變量。看看如下經典的數組遍歷:github
for(let i = 0, l = list.length; i < l; i++) { // do something with list[i] } console.log(i); // undefined
舉個例子,一般狀況下,咱們在同一做用域裏使用變量 j
來完成另外一個遍歷。可是,如今有了 let
,能夠安全地再一次聲明 i
變量。由於它只在被聲明的塊中有效。web
還有另外一個用於聲明塊做用域變量的方法。使用 const
,你能夠聲明一個值的只讀引用。必須直接給一個變量賦值。若是嘗試修改變量或者沒有當即給變量賦值,都將報錯:typescript
const MY_CONSTANT = 1; MY_CONSTANT = 2 // Error const SOME_CONST; // Error
注意,對象的屬性或數組成員仍是能夠改變的。npm
const MY_OBJECT = {some: 1}; MY_OBJECT.some = 'body'; // Cool
箭頭函數爲 Javascript 語言增色很多。它使得代碼更簡潔。咱們早早地在本文中介紹箭頭函數,這樣就能夠在後面的示例中加以利用了。如下代碼片斷是箭頭函數和咱們熟悉的 ES5 版本的寫法:gulp
let books = [{title: 'X', price: 10}, {title: 'Y', price: 15}]; let titles = books.map( item => item.title ); // ES5 equivalent: var titles = books.map(function(item) { return item.title; });
運行代碼數組
若是咱們觀察箭頭函數的語法,會發現其中並無出現 function
關鍵詞。只保留零或多個參數,「胖箭頭」(=>
)和函數表達式。return
聲明被隱式加入。
帶有零或多個參數時,必須使用括號:
// No arguments books.map( () => 1 ); // [1, 1] // Multiple arguments [1,2].map( (n, index) => n * index ); // [0, 2]
若是須要寫更多的邏輯或更多的空格,能夠把函數表達式放在({...}
)塊中。
let result = [1, 2, 3, 4, 5].map( n => { n = n % 3; return n; });
箭頭函數不單只是爲了輸入更少的字符,它們的表現也和通常函數不一樣。它繼承了當前上下文的 this
和 arguments
。這就意味着,你能夠避免寫 var that = this
這樣醜陋的代碼,也不須要把函數綁定到正確的上下文了。舉例以下(注意對比 this.title
和 ES5 版本的 that.title
的不一樣):
let book = { title: 'X', sellers: ['A', 'B'], printSellers() { this.sellers.forEach(seller =&gt;console.log(seller + ' sells ' + this.title)); } } // ES5 equivalent: var book = { title: 'X', sellers: ['A', 'B'], printSellers: function() { var that = this; this.sellers.forEach(function(seller) { console.log(seller + ' sells ' + that.title) }) } }
幾個方便的方法被添加到 String
的原型中。其中大多數用於簡化須要用 indexOf()
方法來解決的問題的複雜度,並達到一樣的效果:
'my string'.startsWith('my'); //true 'my string'.endsWith('my'); // false 'my string'.includes('str'); // true
很簡單可是頗有效。添加了另一個方便的用於建立重複字符串的方法:
'my '.repeat(3); // 'my my my '
模板字符串提供一個簡潔的方式來實現字符串插值。你可能已經對這種語法很熟悉了;它基於美圓符號和花括號 ${..}
。模板字符串置於引號之中。如下是快速示例:
let name = 'John', apples = 5, pears = 7, bananas = function() { return 3; } console.log(`This is ${name}.`); console.log(`He carries ${apples} apples, ${pears} pears, and ${bananas()} bananas.`); // ES5 equivalent: console.log('He carries ' + apples + ' apples, ' + pears + ' pears, and ' + bananas() +' bananas.');
以上形式對比 ES5 僅僅是便於字符串拼接。事實上,模板字符串還能夠用於多行字符串。記住空格也是字符串的一部分。
let x = `1... 2... 3 lines long!`; // Yay // ES5 equivalents: var x = "1...n" + "2...n" + "3 lines long!"; var x = "1...n2...n3 lines long!";
Array
對象增長了一些新的靜態方法,Array
原型上也增長了一些新方法。
首先, Array.from
從類數組和可遍歷對象中建立 Array
的實例。類數組對象示例包括:
arguments
;document.getElementByTagName()
返回的 nodeList
;Map
和 Set
數據結構。
let itemElements = document.querySelectorAll('.items'); let items = Array.from(itemElements); items.forEach(function(element) { console.log(element.nodeType) }); // A workaround often used in ES5: let items = Array.prototype.slice.call(itemElements);
在上面的例子中,能夠看到 items
數組擁有 forEach
方法,該方法是 itemElements
集合所不具有的。
Array.from
的一個有趣的特性是它的第二個可選參數mapFunction
。該參數容許你經過一次單獨調用建立一個新的映射數組。
let navElements = document.querySelectorAll('nav li');
let navTitles = Array.from(navElements, el => el.textContent);
而後,咱們可使用 Array.of
方法,該方法的表現很像 Array
構造函數。它適合只傳遞一個參數的狀況。所以 Array.of
是 new Array()
的更優選擇。然而,更多的狀況下,你會想使用數組字面量。
let x = new Array(3); // [undefined, undefined, undefined] let y = Array.of(8); // [8] let z = [1, 2, 3]; // Array literal
最後但一樣重要的,有幾個方法被添加到 Array
的原型上。我想 find
方法將會很受 Javascript 開發者歡迎。
find
返回回調返回 true
的第一個元素。findIndex
返回回調函數返回 true
的第一個元素的下標。fill
用所給參數「覆蓋」數組的元素。[5, 1, 10, 8].find(n => n === 10) // 10 [5, 1, 10, 8].findIndex(n => n === 10) // 2 [0, 0, 0].fill(7) // [7, 7, 7] [0, 0, 0, 0, 0].fill(7, 1, 3) // [0, 7, 7, 7, 0]
Math
對象新增了幾個方法。
Math.sign
返回數字的符號,結果爲 1
、-1
或 0
。Math.trunc
返回無小數位的數字。Math.cbrt
返回數字的立方根。Math.sign(5); // 1 Math.sign(-9); // -1 Math.trunc(5.9); // 5 Math.trunc(5.123); // 5 Math.cbrt(64); // 4
若是你想學習更多 ES6 中的 number 和 math 新特性, Dr. Axel Rauschmayer將爲你解答。
擴展操做符(...
)這個語法用於特定地方擴展元素很是方便,例如函數調用中的參數。讓你瞭解它們用途的最好方法就是舉例子了。
首先,咱們看看如何在一個另數組中擴展一個數組的元素。
let values = [1, 2, 4]; let some = [...values, 8]; // [1, 2, 4, 8] let more = [...values, 8, ...values]; // [1, 2, 4, 8, 1, 2, 4] // ES5 equivalent: let values = [1, 2, 4]; // Iterate, push, sweat, repeat... // Iterate, push, sweat, repeat...
擴展語法在傳參數調用函數時也很是有用:
let values = [1, 2, 4]; doSomething(...values); function doSomething(x, y, z) { // x = 1, y = 2, z = 4 } // ES5 equivalent: doSomething.apply(null, values);
正如你所看到的,該語法讓咱們免去一般使用 fn.apply()
的麻煩。它很是靈活,由於擴展操做符能夠用在參數列表中的任意位置。這意味着如下調用方式會產生同樣的結果:
let values = [2, 4];
doSomething(1, ...values);
咱們已經把擴展操做符應用在數組和參數中。事實上,它能夠用在全部的可遍歷對象中,例如一個 NodeList
:
let form = document.querySelector('#my-form'), inputs = form.querySelectorAll('input'), selects = form.querySelectorAll('select'); let allTheThings = [form, ...inputs, ...selects];
如今, allTheThings
是一個包含 <form>
節點、 <input>
子節點和 <select>
子節點的二維數組。
解構提供了一個方便地從對象或數組中提取數據的方法。對於初學者,請看如下數組示例:
let [x, y] = [1, 2]; // x = 1, y = 2 // ES5 equivalent: var arr = [1, 2]; var x = arr[0]; var y = arr[1];
使用這個語法,能夠一次性給多個變量賦值。一個很好的附加用處是能夠很簡單地交換變量值:
let x = 1, y = 2; [x, y] = [y, x]; // x = 2, y = 1
解構也能夠用於對象。注意對象中必須存在對應的鍵:
JavaScript
let obj = {x: 1, y: 2}; let {x, y} = obj; // x = 1, y = 2
你也可使用該機制來修改變量名:
JavaScript
let obj = {x: 1, y: 2}; let {x: a, y: b} = obj; // a = 1, b = 2
另外一個有趣的模式是模擬多個返回值:
function doSomething() { return [1, 2] } let [x, y] = doSomething(); // x = 1, y = 2
解構能夠用來爲參數對象賦默認值。經過對象字面量,能夠模擬命名參數:
function doSomething({y = 1, z = 0}) { console.log(y, z); } doSomething({y: 2});
參數
在 ES6 中,能夠定義函數的參數默認值。語法以下:
function doSomething(x, y = 2) { return x * y; } doSomething(5); // 10 doSomething(5, undefined); // 10 doSomething(5, 3); // 15
看起來很簡潔,對吧? 我確定你以前在 ES5 中曾經須要給某些參數賦默認值:
function doSomething(x, y) { y = y === undefined ? 2 : y; return x * y; }
傳遞 undefined
或不傳參數時都會觸發參數使用默認值。
咱們已經學習了省略號操做符。剩餘參數和它很相似。它一樣是使用 ...
語法,容許你把末尾的參數保存在數組中:
function doSomething(x, ...remaining) { return x * remaining.length; } doSomething(5, 0, 0, 0); // 15
模塊固然是一個受歡迎的 Javascript 語言新功能。我想僅僅是這個主要特性就值得咱們投入到 ES6 中來。
當前任何重要的 Javascript 項目都使用某種模塊系統 —— 多是「展現模塊模式」或其餘 AMD 或 CommonJS 擴展形式的東西。然而,瀏覽器並無任何模塊系統特性。爲了實現 AMD 或 CommonJS,你一般須要一個構建步驟或加載器。解決這個問題的工具包括 RequireJS、Browserify 和 WebPack。
ES6 規範包含模塊化的新語法和加載器。若是你將來想使用模塊,應該使用這個語法。現代構建工具支持這種形式(可能經過插件),因此你能夠放心使用。(不用擔憂 —— 咱們將在後面的「轉譯」章節中討論)
在 ES6 的模塊語法中。模塊設計圍繞 export
和 import
關鍵詞。如今讓咱們看一個包含兩個模塊的例子:
// lib/math.js export function sum(x, y) { return x + y; } export var pi = 3.141593; // app.js import { sum, pi } from "lib/math"; console.log('2π = ' + sum(pi, pi));
正如你所見,能夠存在多個 export
聲明。每個都要明確地指明輸出值的類型(本例中的function
和 var
)。
本例中的 import
聲明使用一種語法(相似解構)來明肯定義被導入的內容。可使用 *
通配符,結合 as
關鍵詞給模塊提供一個本地名稱,把模塊當成一個總體導入。
// app.js import * as math from "lib/math"; console.log('2π = ' + math.sum(math.pi, math.pi));
模塊系統有一個 default
輸出。它能夠是一個函數。只須要提供一個本地名稱就能夠導入這個默認值(即無解構):
// lib/my-fn.js export default function() { console.log('echo echo'); } // app.js import doSomething from 'lib/my-fn'; doSomething();
請注意 import
聲明是同步的,可是模塊代碼需在全部依賴加載完後纔會運行。
類是 ES6 中備受熱議的一個特性。一部分人認爲它不符合 Javascript 的原型特性,另外一部分人認爲類能夠下降從其餘語言轉過來的入門門檻,並幫助人們構建大規模應用。無論怎樣,它是 ES6 的一部分。這裏咱們快速介紹一下。
類的建立圍繞 class
和 constructor
關鍵詞。如下是個簡短的示例:
class Vehicle { constructor(name) { this.name = name; this.kind = 'vehicle'; } getName() { return this.name; } } // Create an instance let myVehicle = new Vehicle('rocky');
注意類的定義不是通常的對象,所以,類的成員間沒有逗號。
創造一個類的對象時,須要使用 new
關鍵詞。繼承一個基類時,使用 extends
:
class Car extends Vehicle { constructor(name) { super(name); this.kind = 'car' } } let myCar = new Car('bumpy'); myCar.getName(); // 'bumpy' myCar instanceof Car; // true myCar instanceof Vehicle; //true
從衍生類中,你可使用從任何構造函數或方法中使用 super
來獲取它的基類:
super()
調用父類構造函數。super.getName()
。還有更多關於類的內容。若是你想深刻了解,我推薦 Dr.Axel Rauschmayer 的 《Classes in ECAMScript 6》
記號是一個新的原生數據類型,像 Number
和 String
同樣。你可使用記號爲對象屬性建立惟一標識或建立惟一的常量。
const MY_CONSTANT = Symbol(); let obj = {}; obj[MY_CONSTANT] = 1;
注意經過記號產生的鍵值對不能經過 Object.getOwnPropertyNames()
得到,在 for...in
遍歷、 Object.keys()
、JSON.stringify()
中均不可見。這是與基於字符串的鍵相反的。你能夠經過 Object.getOwnPropertySymbols()
獲取一個對象的記號數組。
記號與 const
配合很合適,由於它們都有不可改變的特性。
const CHINESE = Symbol(); const ENGLISH = Symbol(); const SPANISH = Symbol(); switch(language) { case CHINESE: // break; case ENGLISH: // break; case SPANISH: // break; default: // break; }
你能夠爲 symbol 添加描述。雖然不能夠經過描述獲取 symbol,可是可用於代碼調試。
const CONST_1 = Symbol('my symbol'); const CONST_2 = Symbol('my symbol'); typeof CONST_1 === 'symbol'; // true CONST_1 === CONST_2; // false
想學習更多關於 symbols 的內容嗎?Mozilla 開發者網絡有一個關於該新的 symbol primitive的文章。
咱們如今能夠用 ES6 來寫代碼了。正如介紹中提到的,瀏覽器對 ES6 特性的支持尚不普遍,且各瀏覽器也各不相同。頗有可能你寫的的代碼在用戶的瀏覽器中不能徹底解析。這就是咱們爲何須要把代碼轉換成能在當前的任何瀏覽器中良好運行的舊版本 Javascript(ES5) 。這種轉換一般稱爲「轉譯」。咱們必須在應用中這麼作,直到全部咱們想兼容的瀏覽器都能運行 ES6 爲止。
轉譯代碼並不難。你能夠經過命令行直接轉譯代碼,也能夠把它做爲 Grunt 或 Gulp 之類的任務管理器的插件包含進來。有不少轉譯解決方案,包括 Babel,Traceur 和 TypeScript。例如, 經過 Babel(以前的 「6to5」) 開始使用 ES6 的多種方式 。大多數 ES6 特性供你自由使用。
既然你對 ES6 充滿熱情和期待,爲何不開始使用它呢。根據你想使用的特性和須要兼容的瀏覽器或環境(好比 Node.js),你可能須要在工做流中引入轉譯器。若是你肯定要使用它們,文件監聽器和瀏覽器動態刷新器可使你的編碼體驗更加流暢。
若是你是從零開始,你可能只想經過命令行轉譯代碼(能夠從 Babel CLI documentation 查看示例)。若是你已經使用任務運行器,如 Grunt 或 Gulp,你能夠添加相似 gulp-babel 或Webpack babel-loader 的插件。對於 Grunt,可以使用 grunt-babel 和不少其餘 ES6 相關 的插件。Browserify 的用戶可能會想看看 babelify 。
大多數特性能夠被轉換成兼容 ES5 的代碼且開銷很小。其餘的特性則須要額外處理(可由轉譯器提供),可能有性能損失。若是想把玩一下 ES6 並查看轉譯後的代碼的樣子,可使用各類交互環境(也就是 REPL):
注意 TypeScript 不徹底是一個轉譯器。它是一個類型化的 Javascript 超集,編譯成 Javascript 代碼。在其它特性中,它支持不少 ES6 特性,很像其餘編譯器。
總的來講,部分 ES6 特性幾乎是能夠「免費」使用的,好比模塊,箭頭函數,剩餘參數和類。這些特性只需很小的開銷就能夠被轉譯成 ES5 。Array
、String
和 Math
對象和原型的附加方法(如 Array.from()
和 "it".startsWith("you")
)須要所謂的「polyfills」。Polyfills 是瀏覽器未原生支持的功能的臨時補充。你能夠先加載 profill,而後你的代碼就能夠在瀏覽器中運行,彷彿瀏覽器有這個功能同樣。Babel 和 Traceur 都提供這種 polyfills。
可在Kangax 的 ES6 兼容性表格 中查看轉譯器和瀏覽器支持的 ES6 特性的完整概述。在寫本文時,最新的瀏覽器已經支持 55% 到 70% 以上 ES6 特性了,看到這個真是鼓舞人心啊。Microsoft Edge、Google Chrome 和 Mozilla 的 Firefox 已經在這方面相互競爭了,這對 web 技術整體來講是很是好的。
就我的而言,能夠輕鬆地使用模塊、箭頭函數和剩餘參數之類的 ES6 新特性對於個人代碼是一個極大的提升,是生產力的解放。既然我已經習慣了寫 ES6 代碼並轉譯成 ES5,隨着時間的推移,更多的 ES6 的好處將會天然顯現。
一旦你安裝了轉譯器,你可能新從 let
和箭頭函數之類的「小」特性開始使用。記住本來就是用 ES5 寫的代碼將不會被轉譯器轉譯。當你使用 ES6 來提升你的代碼,而且喜歡它時,你能夠逐漸往你的代碼中添加更多的 ES6 特性。或者把部分代碼轉換成新模塊或類語法。我保證這樣會很爽!
ES6 的內容比本文中所涉及的多得多。未涉及的特性包括 Map
、Set
、標籤模板字符串、生成器、Proxy
和 Promise
。讓我知道你是否但願下篇文章涉及這些特性。無論怎樣,我很高興推薦 Axel Rauschmayer 博士寫的覆蓋全部 ES6 特性的《Exploring ES6》供深刻研究。
當瀏覽器不斷添加新特性時, 經過使用轉譯器,你的全部代碼被有效地「鎖定」到 ES5 。因此,就算瀏覽器徹底支持了某一個 ES6 特性,兼容 ES5 的版本將被使用,可能會性能更差。你能夠期望在某個點(在當時你須要兼容的瀏覽器和環境)上全部的 ES6 特性最終被支持。到那時,咱們須要管理它們,選擇性地防止 ES6 特性轉譯成 ES5,以減小性能開銷。考慮這個因素,判斷當前是不是開始使用(部分)ES6 的時候。部分公司認爲是這樣。