這是一個 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳實踐和一些代碼片斷,幫助你
完成日復一日的開發工做。javascript
除了var
之外,咱們如今多了兩個新的標識符來聲明變量的存儲,它們就是let
和const
。
不一樣於var
,let
和const
語句不會形成聲明提高。
一個 var
的例子:html
var snack = 'Meow Mix'; function getFood(food) { if (food) { var snack = 'Friskies'; return snack; } return snack; } getFood(false); // undefined
讓咱們再觀察下面語句中,使用 let
替換了 var
後的表現:java
let snack = 'Meow Mix'; function getFood(food) { if (food) { let snack = 'Friskies'; return snack; } return snack; } getFood(false); // 'Meow Mix'
當咱們重構使用 var
的老代碼時,必定要注意這種變化。盲目使用 let
替換 var
後可能會致使預期意外的結果。node
注意:let
和const
是塊級做用域語句。因此在語句塊之外引用這些變量時,會形成引用錯誤ReferenceError
。
console.log(x); let x = 'hi'; // ReferenceError: x is not defined
最佳實踐: 在重構老代碼時,var
聲明須要格外的注意。在建立一個新項目時,使用let
聲明一個變量,使用const
來聲明一個不可改變的常量。
咱們以往建立一個 當即執行函數 時,通常是在函數最外層包裹一層括號。
ES6支持塊級做用域(更貼近其餘語言),咱們如今能夠經過建立一個代碼塊(Block)來實現,沒必要經過建立一個函數來實現,react
(function () { var food = 'Meow Mix'; }()); console.log(food); // Reference Error
使用支持塊級做用域的ES6的版本:git
{ let food = 'Meow Mix'; } console.log(food); // Reference Error
一些時候,咱們在函數嵌套中須要訪問上下文中的 this
。好比下面的例子:es6
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined }); };
一種通用的方式是把上下文中的 this
保存在一個變量裏:github
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; }); };
咱們也能夠把 this
經過屬性傳進去:json
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); };
還能夠直接使用 bind
:api
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); };
使用 箭頭函數,this
的值不用咱們再作如上幾段代碼的特殊處理,直接使用便可。
上面的代碼能夠重寫爲下面這樣:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); };
最佳實踐:使用箭頭函數,不再用考慮
this
的問題了。
當咱們編寫只返回一個表達式值的簡單函數時,也可使用箭頭函數,以下:
var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // Arrow Function for terser implementation
最佳實踐:儘量地多使用 箭頭函數。
在ES6中,標準庫也被一樣加強了,像字符串對象就新增了 .includes()
和 .repeat()
方法。
var string = 'food'; var substring = 'foo'; console.log(string.indexOf(substring) > -1);
如今,咱們可使用 .inclues()
方法,替代以往判斷內容 > -1
的方式。.includes()
方法會極簡地返回一個布爾值結果。
const string = 'food'; const substring = 'foo'; console.log(string.includes(substring)); // true
function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(''); }
在ES6中,咱們可使用一個極簡的方法來實現重複字符:
// String.repeat(numberOfRepetitions) 'meow'.repeat(3); // 'meowmeowmeow'
使用 字符串模板字面量,我能夠在字符串中直接使用特殊字符,而不用轉義。
var text = "This string contains \"double quotes\" which are escaped.";
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
字符串模板字面量 還支持直接插入變量,能夠實現字符串與變量的直接鏈接輸出。
var name = 'Tiger'; var age = 13; console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
更簡單的版本:
const name = 'Tiger'; const age = 13; console.log(`My cat is named ${name} and is ${age} years old.`);
ES5中,咱們要這樣生成多行文本:
var text = ( 'cat\n' + 'dog\n' + 'nickelodeon' );
或者:
var text = [ 'cat', 'dog', 'nickelodeon' ].join('\n');
字符串模板字面量 讓咱們沒必要特別關注多行字符串中的換行轉義符號,直接換行便可:
let text = ( `cat dog nickelodeon` );
字符串模板字面量 內部可使用表達式,像這樣:
let today = new Date(); let text = `The time and date is ${today.toLocaleString()}`;
解構讓咱們可使用很是便捷的語法,直接將數組或者對象中的值直接分別導出到多個變量中,
解構數組
var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2
解構對象
var luke = { occupation: 'jedi', father: 'anakin' }; var occupation = luke.occupation; // 'jedi' var father = luke.father; // 'anakin'
let luke = { occupation: 'jedi', father: 'anakin' }; let {occupation, father} = luke; console.log(occupation); // 'jedi' console.log(father); // 'anakin'
ES6以前,瀏覽器端的模塊化代碼,咱們使用像Browserify這樣的庫,
在 Node.js 中,咱們則使用 require。
在ES6中,咱們如今能夠直接使用AMD 和 CommonJS這些模塊了。
module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {};
在ES6中,提供了多種設置模塊出口的方式,好比咱們要導出一個變量,那麼使用 變量名 :
export let name = 'David'; export let age = 25;
還能夠爲對象 導出一個列表:
function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree };
咱們也可使用簡單的一個 export
關鍵字來導出一個結果值:
export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; }
最後,咱們能夠 導出一個默認出口:
function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api;
最佳實踐:老是在模塊的 最後 使用export default
方法。
它讓模塊的出口更清晰明瞭,節省了閱讀整個模塊來尋找出口的時間。
更多的是,在大量CommonJS模塊中,通用的習慣是設置一個出口值或者出口對象。
最受這個規則,可讓咱們的代碼更易讀,且更方便的聯合使用CommonJS和ES6模塊。
ES6提供了好幾種模塊的導入方式。咱們能夠單獨引入一個文件:
import 'underscore';
這裏須要注意的是, 整個文件的引入方式會執行該文件內的最上層代碼。
就像Python同樣,咱們還能夠命名引用:
import { sumTwo, sumThree } from 'math/addition';
咱們甚至可使用 as
給這些模塊重命名:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from 'math/addition';
另外,咱們能 引入全部的東西(原文:import all the things) (也稱爲命名空間引入)
import * as util from 'math/addition';
最後,咱們能能夠從一個模塊的衆多值中引入一個列表:
import * as additionUtil from 'math/addtion'; const { sumTwo, sumThree } = additionUtil;
像這樣引用默認對象:
import api from 'math/addition'; // Same as: import { default as api } from 'math/addition';
咱們建議一個模塊導出的值應該越簡潔越好,不過有時候有必要的話命名引用和默認引用能夠混着用。若是一個模塊是這樣導出的:
// foos.js export { foo as default, foo1, foo2 };
那咱們能夠如此導入這個模塊的值:
import foo, { foo1, foo2 } from 'foos';
咱們還能夠導入commonjs模塊,例如React:
import React from 'react'; const { Component, PropTypes } = React;
更簡化版本:
import React, { Component, PropTypes } from 'react';
注意:被導出的值是被 綁定的(原文:bingdings),而不是引用。
因此,改變一個模塊中的值的話,會影響其餘引用本模塊的代碼,必定要避免此種改動發生。
在ES5中,許多種方法來處理函數的 參數默認值(default values),參數數量(indefinite arguments),參數命名(named parameters)。
ES6中,咱們可使用很是簡潔的語法來處理上面提到的集中狀況。
function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; }
ES6中,咱們能夠簡單爲函數參數啓用默認值:
function addTwoNumbers(x=0, y=0) { return x + y; }
addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0
ES5中,遇到參數數量不肯定時,咱們只能如此處理:
function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } }
使用 rest 操做符,咱們能夠給函數傳入一個不肯定數量的參數列表:
function logArguments(...args) { for (let arg of args) { console.log(arg); } }
命名函數
ES5中,當咱們要處理多個 命名參數 時,一般會傳入一個 選項對象 的方式,這種方式被jQuery採用。
function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || 'black'; }
咱們能夠利用上面提到的新特性 解構 ,來完成與上面一樣功能的函數:
We can achieve the same functionality using destructuring as a formal parameter
to a function:
function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // ... } // Use variables height, width, lineStroke here
若是咱們須要把這個參數變爲可選的,那麼只要把該參數解構爲一個空對象就行了:
function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... }
咱們能夠利用展開操做符(Spread Operator)來把一組數組的值,看成參數傳入:
Math.max(...[-1, 100, 9001, -32]); // 9001
在ES6之前,咱們實現一個類的功能的話,須要首先建立一個構造函數,而後擴展這個函數的原型方法,就像這樣:
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; };
繼承父類的子類須要這樣:
function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { return Person.prototype.incrementAge.call(this) += 20; };
ES6提供了一些語法糖來實現上面的功能,咱們能夠直接建立一個類:
class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } }
繼承父類的子類只要簡單的使用 extends
關鍵字就能夠了:
class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } }
最佳實踐:ES6新的類語法把咱們從晦澀難懂的實現和原型操做中解救出來,這是個很是適合初學者的功能,並且能讓咱們寫出更乾淨整潔的代碼。
符號(Symbols)在ES6版本以前就已經存在了,但如今咱們擁有一個公共的接口來直接使用它們。
Symbols對象是一旦建立就不能夠被更改的(immutable)並且能被用作hash數據類型中的鍵。
調用 Symbol()
或者 Symbol(描述文本)
會建立一個惟一的、在全局中不能夠訪問的符號對象。
一個 Symbol()
的應用場景是:在本身的項目中使用第三方代碼庫,且你須要給他們的對象或者命名空間打補丁代碼,又不想改動或升級第三方原有代碼的時候。
舉個例子,若是你想給 React.Component
這個類添加一個 refreshComponent
方法,但又肯定不了這個方法會不會在下個版本中加入,你能夠這麼作:
const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something }
使用 Symbol.for(key)
也是會建立一個不可改變的Symbol對象,但區別於上面的建立方法,這個對象是在全局中能夠被訪問到的。
調用兩次 Symbol.for(key)
會返回相同的Symbol實例。
提示:這並不一樣於 Symbol(description)
。
Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true
一個Symbols經常使用的使用場景,是須要使用特別 Symbol.for(key)
方法來實現代碼間的協做。
這能讓你在你的代碼中,查找包含已知的接口的第三方代碼中Symbol成員。(譯者:這句話好難翻。。。原文:This can be
achieved by having your code look for a Symbol member on object arguments from third parties that contain some known interface. )舉個例子:
function reader(obj) { const specialRead = Symbol.for('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError('object cannot be read'); } }
以後在另外一個庫中:
const specialRead = Symbol.for('specialRead'); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } }
注意:
Symbol.iterable
在ES6中像其餘可枚舉的對象,如數組,字符串,generators同樣,當這個方法被調用時會激活一個枚舉器並返回一個對象。
Maps 是一個Javascript中很重要(迫切須要)的數據結構。
在ES6以前,咱們建立一個 hash 一般是使用一個對象:
var map = new Object(); map[key1] = 'value1'; map[key2] = 'value2';
可是,這樣的代碼沒法避免函數被特別的屬性名覆蓋的意外狀況:
getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned'); TypeError: Property 'hasOwnProperty' is not a function
Maps 讓咱們使用 set
,get
和 search
操做數據。
let map = new Map(); map.set('name', 'david'); map.get('name'); // david map.has('name'); // true
Maps最強大的地方在於咱們沒必要只能使用字符串來作key了,如今可使用任何類型來看成key,並且key不會被強制類型轉換爲字符串。
let map = new Map([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [function () {}, 'function'] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function }
提示:當使用map.get()
判斷值是否相等時,非基礎類型好比一個函數或者對象,將不會正常工做。
有鑑於此,仍是建議使用字符串,布爾和數字類型的數據類型。
咱們還可使用 .entries()
方法來遍歷整個map對象:
for (let [key, value] of map.entries()) { console.log(key, value); }
在ES5以前的版本,咱們爲了存儲私有數據,有好幾種方法。像使用這種下劃線命名約定:
class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } }
在一個開源項目中,命名規則很難維持得一直很好,這樣常常會形成一些困擾。
此時,咱們能夠選擇使用WeakMaps來替代Maps來存儲咱們的數據:
let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log('Midlife crisis'); } } }
使用WeakMaps來保存咱們私有數據的理由之一是不會暴露出屬性名,就像下面的例子中的 Reflect.ownKeys()
:
const person = new Person(50); person.incrementAge(); // 'Midlife crisis' Reflect.ownKeys(person); // []
一個使用WeakMaps存儲數據更實際的例子,就是有關於一個DOM元素和對該DOM元素(有污染)地操做:
let map = new WeakMap(); let el = document.getElementById('someElement'); // Store a weak reference to the element with a key map.set(el, 'reference'); // Access the value of the element let value = map.get(el); // 'reference' // Remove the reference el.parentNode.removeChild(el); el = null; value = map.get(el); // undefined
上面的例子中,一個對象被垃圾回收期給銷燬了,WeakMaps會自動的把本身內部所對應的鍵值對數據同時銷燬。
提示:結合這個例子,再考慮下jQuery是如何實現緩存帶有引用的DOM元素這個功能的,使用了WeakMaps的話,當被緩存的DOM元素被移除的時,jQuery能夠自動釋放相應元素的內存。
一般狀況下,在涉及DOM元素存儲和緩存的狀況下,使用WeakMaps是很是適合的。
Promises讓咱們讓咱們多縮進難看的代碼(回調地獄):
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); });
寫成這樣:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 });
在ES6以前,咱們使用bluebird 或者
Q。如今咱們有了原生版本的 Promises:
new Promise((resolve, reject) => reject(new Error('Failed to fulfill Promise'))) .catch(reason => console.log(reason));
這裏有兩個處理函數,resolve(當Promise執行成功完畢時調用的回調函數) 和 reject (當Promise執行不接受時調用的回調函數)
Promises的好處:大量嵌套錯誤回調函數會使代碼變得難以閱讀理解。
使用了Promises,咱們可讓咱們代碼變得更易讀,組織起來更合理。
此外,Promise處理後的值,不管是解決仍是拒絕的結果值,都是不可改變的。
下面是一些使用Promises的實際例子:
var fetchJSON = function(url) { return new Promise((resolve, reject) => { $.getJSON(url) .done((json) => resolve(json)) .fail((xhr, status, err) => reject(status + err.message)); }); };
咱們還可使用 Promise.all()
來異步的 並行 處理一個數組的數據。
var urls = [ 'http://www.api.com/items/1234', 'http://www.api.com/items/4567' ]; var urlPromises = urls.map(fetchJSON); Promise.all(urlPromises) .then(function (results) { results.forEach(function (data) { }); }) .catch(function (err) { console.log('Failed: ', err); });
就像Promises如何讓咱們避免回調地獄同樣,Generators也可使咱們的代碼扁平化,同時給予咱們開發者像開發同步代碼同樣的感受來寫異步代碼。Generators本質上是一種支持的函數,隨後返回表達式的值。
Generators其實是支持暫停運行,隨後根據上一步的返回值再繼續運行的一種函數。
下面代碼是一個使用generators函數的簡單例子:
function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: 4, done: false }
就像上面的例子,當next運行時,它會把咱們的generator向前「推進」,同時執行新的表達式。
咱們能利用Generators來像書寫同步代碼同樣書寫異步代碼。
// Hiding asynchronousity with Generators function request(url) { getJSON(url, function(response) { generator.next(response); }); }
這裏咱們寫個generator函數將要返回咱們的數據:
function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); }
藉助於 yield
,咱們能夠保證 entry1
確實拿到數據並轉換後再賦值給 data1
。
當咱們使用generators來像書寫同步代碼同樣書寫咱們的異步代碼邏輯時,沒有一種清晰簡單的方式來處理期間可能會產生的錯誤或者異常。在這種狀況下,咱們能夠在咱們的generator中引入Promises來處理,就像下面這樣:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); }
咱們再寫一個函數,其中使用 next
來步進咱們的generator的同事,再利用咱們上面的 request
方法來產生(yield)一個Promise。
function iterateGenerator(gen) { var generator = gen(); var ret; (function iterate(val) { ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); }
在Generator中引入了Promises後,咱們就能夠經過Promise的 .catch
和 reject
來捕捉和處理錯誤了。
使用了咱們新版的Generator後,新版的調用就像老版本同樣簡單可讀(譯者注:有微調):
iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); });
在使用Generator後,咱們能夠重用咱們的老版本代碼實現,以此展現了Generator的力量。
當使用Generators和Promises後,咱們能夠像書寫同步代碼同樣書寫異步代碼的同時優雅地解決了錯誤處理問題。
此後,咱們實際上能夠開始利用更簡單的一種方式了,它就是async-await。
async await
隨着ES2016版本就要發佈了,它給咱們提供了一種更輕鬆的、更簡單的能夠替代的實現上面 Generators 配合 Promises 組合代碼的一種編碼方式,讓咱們來看看例子:
var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main();
不斷更新、修復...