ES6在2015年6月就得以批准,至今已兩年了。近一年多以來陸續看過不少ES6的資料,工做項目中也逐步的用上了不少ES6的特性(
let
,const
,promise
,Template strings
,Class
,箭頭函數等等),不得不說,這些特性給開發帶來了很是多的便利。可是作決定個人ES6知識其實並不夠系統,這也是寫本文的初衷,但願閱讀本文能讓你也能對ES6有更系統的理解,本文並非那種大而全的教程,而是但願在實際工做中,能想起某個新特性能夠解決你當前的問題或者優化當前的代碼,以後再系統學習,應用,用過了確定就會真的掌握了。javascript本文基於Github上的高贊文章ECMAScript 6 Features/Babel修改過的Learn ES2015,翻譯(寫做?)期間,重溫了阮一峯老師的ECMAScript 6 入門。前端
相比ES5,ES6提供了太多的更新,簡單說來,主要爲如下方面(你們能夠依據本身不算清晰的點選擇性查看本文):java
Arrows,箭頭函數,jquery
Classes,類git
Enhanced object literals,加強的對象字面值es6
Template strings:模板字符串github
Destructuring:解構算法
Default + rest + spread:參數默認值,rest參數,擴展運算符編程
Let + const:命名聲明的新方式json
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:操做對象的新api
Tail calls:尾調用
箭頭函數使用相似於=>
這樣的語法定義函數,支持表達式模式和語句模式,不過其最大特色在於和父做用域具備同樣的this
。咱們知道普通函數的this
既不指向函數自身也不指向函數的詞法做用域,this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。使用箭頭函數時不再用擔憂this
跳來跳去了。
此外若是箭頭函數若是定義在另外一個函數裏面,箭頭函數會共享它父函數的arguments變量。
// 表達式模式箭頭函數 var odds = evens.map(v => v + 1); var nums = evens.map((v, i) => v + i); var pairs = evens.map(v => ({even: v, odd: v + 1})); // 語句模式箭頭函數 nums.forEach(v => { if (v % 5 === 0) fives.push(v); }); // 和父做用域具備相同的this var bob = { _name: "Bob", _friends: [], printFriends() { this._friends.forEach(f => console.log(this._name + " knows " + f)); } } function square() { let example = () => { let numbers = []; for (let number of arguments) { numbers.push(number * number); } return numbers; }; return example(); } square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441]
JavaScript中其實並不存在真正的類,ES6的類實際上是基於原型鏈模擬面向對象的一種語法糖。其本質上能夠看作是構造函數的另外一種寫法。
與真的類同樣,它支持super
繼承,實例,靜態方法和constructor
方法。
若是你也使用React,工做中定義模塊時必定沒少寫過class A extends React.Component{}
吧。
// 定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } // 經過extends關鍵字實現繼承 class SkinnedMesh extends THREE.Mesh { //constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。 //一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。 constructor(geometry, materials) { // super表示父類的構造函數,用來新建父類的this對象, // 子類必須在constructor方法中調用super方法,不然新建實例時會報錯。若是不調用super方法,子類就得不到this對象。 super(geometry, materials); //在構造方法中綁定this,能夠防止實例找不到this this.idMatrix = SkinnedMesh.defaultMatrix(); this.bones = []; this.boneMatrices = []; //... } // 非定義在this上的方法都會被直接定義在原型鏈上 update(camera) { //... // super在此處做爲對象,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。 super.update(); } // 可使用get和set關鍵字,對某個屬性設置存值函數和取值函數 get boneCount() { // 類的方法內部若是含有this,它默認指向類的實例 return this.bones.length; } set matrixType(matrixType) { this.idMatrix = SkinnedMesh[matrixType](); } // 加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用 static defaultMatrix() { return new THREE.Matrix4(); } } // 類的全部實例共享一個原型對象 let skin = new SkinnedMesh(); // 靜態方法須要直接經過類調用 SkinnedMesh.defaultMatrix()
ES6中對象的使用方法得以拓展,主要包括如下幾點:
屬性和方法能夠簡潔表示;
容許以表達式的模式定義屬性名;
能夠經過__proto__
讀取或設置當前對象的prototype對象;
使用Object.is({},{})
判斷兩個對象是否徹底相對,相似於===
;
Object.assign(target, source1, source2)
合併對象;(淺拷貝)
var obj = { // __proto__用以設置當前對象的prototype對象,不推薦使用,推薦使用Object.setPrototypeOf() __proto__: theProtoObj, //‘handler:handler’可簡寫爲handler(只須要寫變量名就能夠實現變量名爲變量名,變量值爲屬性值) handler, // 簡寫在定義方法的時候一樣有效 toString() { // Super calls return "d " + super.toString(); }, // 方括號內的表達式用以計算屬性名 [ 'prop_' + (() => 42)() ]: 42 };
模板字符串是一種組合字符串的語法糖,其使用相似於Perl
,Python
等語言的字符串修改方法相似,它的出現讓咱們拼合字符串時方便多了。目前相互中幾乎全部字符串的拼接都用這個了,異常方便。
模板字符串定義在兩個反撇號中;
在模板字符串中能夠直接換行,格式會得以保留;
經過${}
能夠很方便的在模板字符串中添加變量;
// 把字符串放在``(注意不是引號)中就可使用 `In JavaScript '\n' is a line-feed.` // 模板字符串保留了換行 `In JavaScript this is not legal.` // 在字符串中添加變量的方法,變量直接放在${}中便可 var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` // 拼合請求時異常方便了 POST`http://foo.org/bar?a=${a}&b=${b} Content-Type: application/json X-Credentials: ${credentials} { "foo": ${foo}, "bar": ${bar}}`(myOnReadyStateChangeHandler);
解構使用模式匹配的方法綁定變量和值,數組和對象均可使用。解構在綁定失敗的時會實現軟綁定,即沒有匹配值時,返回undefined
。使用方法可見示例:
// 數組解構 var [a, , b] = [1,2,3]; // a = 1,b = 3 // React中常見如下用法 var {a, b, c} = this.props; // 對象解構也能用在函數的參數中 function g({name: x}) { console.log(x); } g({name: 5}) // 綁定失敗時返回undefined var [a] = []; a === undefined; // 解構時也能夠綁定默認值 var [a = 1] = []; a === 1; // 配合默認參數使用結構 function r({x, y, w = 10, h = 10}) { return x + y + w + h; } r({x:1, y:2}) === 23
ES6容許咱們在給變量添加默認值
使用拓展值使得函數調用時可傳入數組做爲連續的參數
利用剩餘值特性咱們能夠把函數尾部的參數轉換爲一個數組,如今使用rest
就能夠替換之前的arguments
對象了。
// 給函數的參數添加默認值 function f(x, y=12) { // y is 12 if not passed (or passed as undefined) return x + y; } // 能夠只傳參數x的值了 f(3) == 15 // 使用rest function f(x, ...y) { // y is an Array return x * y.length; } f(3, "hello", true) == 6 // 傳入數組做爲參數 function f(x, y, z) { return x + y + z; } // 直接傳入數組看成上面函數的參數 f(...[1,2,3]) == 6
ES6新增了塊做用域,新增了兩種定義變量的方法,定義變量時推薦使用let
替代var
,let
定義的變量在塊做用域內有效,const
用以指定固定值,這兩類新定義的變量不容許在定義前使用,也不容許重複定義。
function f() { { let x; { const x = "sneaky"; // 改變const x = "foo"; } // 重複定義會出錯 let x = "inner"; } } // 在這裏想到一個使用var時新手特別容易犯的問題 for (var i=0; i<10; ++i) { setTimeout(function(){ console.log(i); }, i*1000); } // 使用var 全部的結果都是10 // 使用let 結果就是預想要的結果 for (let i=0; i<10; ++i) { setTimeout(function(){ console.log(i); }, i*1000); }
ES6爲部署了Iterator接口的各類不一樣的數據結構提供了統一的訪問機制。其本質是一個指針對象。每次調用next
方法,能夠把指針指向數據結構的下一個成員。具體說來,每一次調用next方法,都會返回數據結構的當前成員的信息(一個包含value和done兩個屬性的對象,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束)。
凡是部署了Symbol.iterator屬性的數據結構,就稱爲部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。
let fibonacci = { // 一個數據結構只要具備Symbol.iterator屬性,就可被認爲是可遍歷的,`Symbol.iterator`是一個表達式,返回Symbol對象的iterator屬性,因此須要放在[]中,本質上它是當前數據結構的遍歷器生成函數。 [Symbol.iterator]() { let pre = 0, cur = 1; return { next() { [pre, cur] = [cur, pre + cur]; return { done: false, value: cur } } } } } // fibonacci部署了Symbol.iterator屬性,只要done不爲true就會一直遍歷 for (var n of fibonacci) { // 調用1000之內的值作遍歷 if (n > 1000) break; console.log(n); }
原生具有
Iterator
接口的數據結構有如下幾種:數組、某些相似數組的對象(字符串、DOM NodeList 對象、arguments對象)、Set和Map結構。
對象(Object)之因此沒有默認部署Iterator接口,是由於對象的哪一個屬性先遍歷,哪一個屬性後遍歷是不肯定的,須要開發者手動在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可)。
實際使用時需引入polyfill
能夠從兩個角度理解Generators
,它既是狀態機也是一個遍歷器對象生成函數。執行該函數能夠理解爲啓動了遍歷器,以後每次執行next()
函數則每次執行到yield
處。
值得注意的是執行next()
時可添加參數,這實現了在函數運行的不一樣階段,能夠從外部向內部注入不一樣的值,
生成器使用function*
和yield
簡化了迭代過程,使用function*
定義的函數返回了一個生成器實例。
生成器是迭代器的子類,可是包含next
和throw
。這使得值能夠迴流到生成器,yield
是一個能夠返回值的表達式。
for...of
循環能夠自動遍歷 Generator
函數時生成的Iterator
對象,此時再也不須要調用next
方法。
Generator
的return
方法會返回固定的值,終結遍歷Generator函數。返回值的value屬性就是return方法的參數,返回值的done屬性爲true。
結合co
模塊能夠實現比Promise更加優雅的異步調用方式
// 使用generator函數實現上述遍歷器對象 var fibonacci = { [Symbol.iterator]: function*() { var pre = 0, cur = 1; for (;;) { var temp = pre; pre = cur; cur += temp; yield cur; } } } for (var n of fibonacci) { // truncate the sequence at 1000 if (n > 1000) break; console.log(n); } // 使用co模塊(基於 Promise 對象的自動執行器),能夠實現異步函數的自動執行 var gen = function* () { var f1 = yield somethingAsync(); var f2 = yield anotherThingAsync(); }; var co = require('co'); co(gen);
實際使用時需引入polyfill
ES6完整支持全部的Unicode,包括新的Unicode
字面量和u
模式正則,提供了新的API來處理21bit
級別的字符串。這些新加特性使得咱們的JavaScript應用有能力支持各類語言。
// same as ES5.1 "?".length == 2 // 新的正則匹配模式 "?".match(/./u)[0].length == 2 // 新形式 "\u{20BB7}"=="?"=="\uD842\uDFB7" // codePointAt()可以正確處理4個字節儲存的字符,返回一個字符的碼點 "?".codePointAt(0) == 0x20BB7 // for-of 遍歷字符,以總體輸出 for(var c of "?") { console.log(c); } // ?
咱們也能夠在JS中寫出Emoji了,頗有趣,對不對:
現代JS應用的開發離不開模塊了,ES6對模塊的定義提供了語言層面的支持。規範化了各類JavaScript模塊加載器,支持運行時動態加載模塊,支持異步加載模塊。
ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量,效率要比 CommonJS 模塊的加載方式高。
// lib/math.js 模塊的定義 export function sum(x, y) { return x + y; } export var pi = 3.141593; // app.js 模塊的所有引用 import * as math from "lib/math"; alert("2π = " + math.sum(math.pi, math.pi)); // otherApp.js 模塊的部分引用 import {sum, pi} from "lib/math"; alert("2π = " + sum(pi, pi)); // 模塊導出方法 // lib/mathplusplus.js export * from "lib/math"; export var e = 2.71828182846; export default function(x) { return Math.log(x); } // 混合引入方法 import ln, {pi, e} from "lib/mathplusplus"; alert("2π = " + ln(e)*pi*2);
模塊加載器支持如下功能:
動態加載
狀態隔離
全局命名空間隔離
編寫鉤子
嵌套
默認的模塊加載器能夠被配置,新的加載器能夠被配置來評估加載獨立上下文中的內容。
// 動態加載 – ‘System’ 是默認的加載器 System.import('lib/math').then(function(m) { alert("2π = " + m.sum(m.pi, m.pi)); }); // 新的加載器建立了執行沙盒 var loader = new Loader({ global: fixup(window) // replace ‘console.log’ }); loader.eval("console.log('hello world!');"); // 能夠直接修改模塊的緩存 System.get('jquery'); System.set('jquery', Module({$: $})); // WARNING: not yet finalized
ES6爲算法提供了新的高效的數據結構,WeakMaps
提供了防泄漏的鍵值對錶。
// Set相似於數組,可是成員的值都是惟一的,沒有重複的值。 var s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true; // Map 相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。 var m = new Map(); m.set("hello", 42); m.set(s, 34); m.get(s) == 34; // WeakMap結構與Map結構相似,也是用於生成鍵值對的集合,可是WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名,此外WeakMap的鍵名所指向的對象,不計入垃圾回收機制。 var wm = new WeakMap(); wm.set(s, { extra: 42 }); wm.size === undefined // WeakSet 結構與 Set 相似,也是不重複的值的集合,可是WeakSet 的成員只能是對象,而不能是其餘類型的值,此外WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用 var ws = new WeakSet(); ws.add({ data: 42 }); // Because the added object has no other references, it will not be held in the set
實際使用時需引入polyfill
Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。
能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。
須要注意的是目前未被Babel支持,使用時需謹慎
// target參數表示所要攔截的目標對象; var target = {}; // handler參數也是一個對象,用來定製攔截行爲; var handler = { get: function (receiver, name) { return `Hello, ${name}!`; } }; // 生成一個Proxy實例 var p = new Proxy(target, handler); p.world === 'Hello, world!'; // 對函數一樣可使用代理 var target = function () { return 'I am the target'; }; var handler = { apply: function (receiver, ...args) { return 'I am the proxy'; } }; var p = new Proxy(target, handler); p() === 'I am the proxy';
// Proxy支持的攔截操做以下
var handler = { get:..., set:..., has:..., deleteProperty:..., apply:..., construct:..., getOwnPropertyDescriptor:..., defineProperty:..., getPrototypeOf:..., setPrototypeOf:..., enumerate:..., ownKeys:..., preventExtensions:..., isExtensible:... }
Babel 不支持,使用時應注意
Symbol
保證每一個屬性的名字都是獨一無二的,這樣就從根本上防止了屬性名的衝突;
它是一種相似於字符串的數據類型,Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述;
Symbols是惟一的,單並不是私有的,經過Object.getOwnPropertySymbols
能夠獲取對應的值;
Symbol 值做爲對象屬性名時,不能用點運算符。
var MyClass = (function() { // module scoped symbol var key = Symbol("key"); function MyClass(privateData) { this[key] = privateData; } MyClass.prototype = { doStuff: function() { ... this[key] ... } }; return MyClass; })(); var c = new MyClass("hello") c["key"] === undefined
因爲語言限制,Babel只提供部分支持,使用時須要注意
在ES6中,內置的Array
,Date
,DOM Element
能夠被繼承以拓展了。
// User code of Array subclass class MyArray extends Array { constructor(...args) { super(...args); } } var arr = new MyArray(); arr[1] = 12; arr.length == 2
babel 部分支持,因爲ES5引擎的限制
Date
,Array
,Error
不被支持,可是HTMLElement
是被支持的
ES6 爲不少舊有對象添加了新的API,這些對象包括Math
,Array器
,String
,Object
,以下:
Number.EPSILON Number.isInteger(Infinity) // false Number.isNaN("NaN") // false Math.acosh(3) // 1.762747174039086 Math.hypot(3, 4) // 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2 "abcde".includes("cd") // true "abc".repeat(3) // "abcabcabc" Array.from(document.querySelectorAll('*')) // Returns a real Array Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior [0, 0, 0].fill(7, 1) // [0,7,7] [1, 2, 3].find(x => x == 3) // 3 [1, 2, 3].findIndex(x => x == 2) // 1 [1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2] ["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"] ["a", "b", "c"].keys() // iterator 0, 1, 2 ["a", "b", "c"].values() // iterator "a", "b", "c" Object.assign(Point, { origin: new Point(0,0) })
babel 經過 polyfill 提供部分支持
ES6添加了二進制和八進制數值的字面量定義方法:
0b111110111 === 503 // true 0o767 === 503 // true
babel 只支持字面量形式,不支持 Number("0o767")形式
Promise爲異步編程提供了一種新的方式,Promise把將來將用到的值當作一等對象,Promise在不少前端庫中已經有所支持了。這個平時用得最多了,還沒使用的推薦試試。
function timeout(duration = 0) { return new Promise((resolve, reject) => { setTimeout(resolve, duration); }) } var p = timeout(1000).then(() => { return timeout(2000); }).then(() => { throw new Error("hmm"); }).catch(err => { return Promise.all([timeout(100), timeout(200)]); })
實際使用時需引入polyfill
Reflect對象與Proxy對象同樣,也是 ES6 爲了操做對象而提供的新 API,做用以下:
將Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上;
修改某些Object方法的返回結果,讓其變得更合理;
讓Object操做都變成函數行爲,好比name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數行爲。
Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法;
var O = {a: 1}; Object.defineProperty(O, 'b', {value: 2}); O[Symbol('c')] = 3; Reflect.ownKeys(O); // ['a', 'b', Symbol(c)] function C(a, b){ this.c = a + b; } var instance = Reflect.construct(C, [20, 22]); instance.c; // 42
實際使用時需引入polyfill
尾部調用被保證不能無限拓展棧,這讓有無限制輸入時的遞歸算法更加安全。
function factorial(n, acc = 1) { 'use strict'; if (n <= 1) return acc; return factorial(n - 1, n * acc); } // 堆棧愈來愈經常使用,在ES6中其使用更加安全了 factorial(100000)
上文對ES6的新特性都作了簡單的描述,可是關於Reflect API
和Proxies
,因爲本人對他們的理解還不夠透徹,說得可能有些不清不楚。但願閱讀本文讓你有收穫,有任何疑問,你們也能夠一塊兒討論。