這是一個 ES2015(ES6) 的Cheatsheet,个中包括提醒、小技能、最好實踐和一些代碼片斷,協助你
完成日復一日的開闢事情。
Table of Contents
- var 與 let / const 聲明
- 代碼實行塊替換馬上實行函數
- 箭頭函數
- 字符串
- 解構
- 模塊
- 參數
- 類
- Symbols
- Maps
- WeakMaps
- Promises
- Generators
- Async Await
var versus let / const
除了
var
之外,我們如今多了兩個新的標識符來聲明變量的存儲,它們就是
let
和
const
。不同於
var
,
let
和
const
語句不會形成聲明提拔。
一個 var
的例子:
var snack = 'Meow Mix';
function getFood(food) {
if (food) {
var snack = 'Friskies';
return snack;
}
return snack;
}
getFood(false); // undefined
讓我們再視察下面語句中,運用 let
替換了 var
后的表現:
let snack = 'Meow Mix';
function getFood(food) {
if (food) {
let snack = 'Friskies';
return snack;
}
return snack;
}
getFood(false); // 'Meow Mix'
當我們重構運用 var
的老代碼時,肯定要注重這類變化。自覺運用 let
替換 var
后可能會致使預期不測的效果。
注重:
let
和
const
是塊級作用域語句。所以在語句塊之外援用這些變量時,會形成援用毛病
ReferenceError
。
console.log(x);
let x = 'hi'; // ReferenceError: x is not defined
最好實踐: 在重構老代碼時,
var
聲明須要分外的注重。在建立一個新項目時,運用
let
聲明一個變量,運用
const
來聲明一個不可轉變的常量。
Replacing IIFEs with Blocks
我們以往建立一個 馬上實行函數 時,平常是在函數最外層包裹一層括號。
ES6支撐塊級作用域(更切近其他言語),我們如今能夠經由過程建立一個代碼塊(Block)來完成,沒必要經由過程建立一個函數來完成,
(function () {
var food = 'Meow Mix';
}());
console.log(food); // Reference Error
運用支撐塊級作用域的ES6的版本:
{
let food = 'Meow Mix';
}
console.log(food); // Reference Error
Arrow Functions
一些時刻,我們在函數嵌套中須要接見高低文中的 this
。比方下面的例子:
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
保留在一個變量里:
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
經由過程屬性傳進去:
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character;
}, this);
};
還能夠直接運用 bind
:
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
最好實踐:盡量地多運用
箭頭函數。
Strings
在ES6中,規範庫也被一樣增強了,像字符串對象就新增了 .includes()
和 .repeat()
要領。
.includes( )
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
.repeat( )
function repeat(string, count) {
var strings = [];
while(strings.length < count) {
strings.push(string);
}
return strings.join('');
}
在ES6中,我們能夠運用一個極簡的要領來完成反覆字符:
// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'
Template Literals
運用 字符串模板字面量,我能夠在字符串中直接運用迥殊字符,而不必轉義。
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()}`;
Destructuring
解構讓我們能夠運用非常便利的語法,直接將數組或許對象中的值直接離別導出到多個變量中,
Destructuring Arrays
解構數組
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
Destructuring Objects
解構對象
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'
Modules
ES6之前,瀏覽器端的模塊化代碼,我們運用像Browserify如許的庫,
在 Node.js 中,我們則運用 require。
在ES6中,我們如今能夠直接運用AMD 和 CommonJS這些模塊了。
Exporting in CommonJS
module.exports = 1;
module.exports = { foo: 'bar' };
module.exports = ['foo', 'bar'];
module.exports = function bar () {};
Exporting in ES6
在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模塊。
Importing in 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),而不是援用。所以,轉變一個模塊中的值的話,會影響其他援用本模塊的代碼,肯定要防止此種修改發作。
Parameters
在ES5中,很多種要領來處置懲罰函數的 參數默許值(default values),參數數目(indefinite arguments),參數定名(named parameters)。
ES6中,我們能夠運用非常簡約的語法來處置懲罰上面提到的集合狀況。
Default Parameters
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
Rest Parameters
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);
}
}
Named Parameters
定名函數
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
我們能夠運用睜開操縱符(Spread Operator)來把一組數組的值,看成參數傳入:
Math.max(...[-1, 100, 9001, -32]); // 9001
Classes
在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
標記(Symbols)在ES6版本之前就已存在了,但如今我們具有一個大眾的接口來直接運用它們。
Symbols對象是一旦建立就不能夠被變動的(immutable)而且能被用做hash數據範例中的鍵。
Symbol( )
挪用 Symbol()
或許 Symbol(形貌文本)
會建立一個唯一的、在全局中不能夠接見的標記對象。
一個 Symbol()
的運用場景是:在本身的項目中運用第三方代碼庫,且你須要給他們的對象或許定名空間打補丁代碼,又不想修改或晉級第三方原有代碼的時刻。
舉個例子,假如你想給 React.Component
這個類增加一個 refreshComponent
要領,但又肯定不了這個要領會不會在下個版本中到場,你能夠這麼做:
const refreshComponent = Symbol();
React.Component.prototype[refreshComponent] = () => {
// do something
}
Symbol.for(key)
運用 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
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);
}
WeakMaps
在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
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);
});
Generators
就像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
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();
不斷更新、修復…