今天群里有小伙伴跟我聊天,問了我?guī)讉€(gè)關(guān)于ES6的問題,我才意識(shí)到,大部分初學(xué)者在學(xué)習(xí)的過程中,都是學(xué)了HTML/CSS/JS之后就開始上手學(xué)習(xí)框架了,而對(duì)于ES6的重視程度卻不是那么足,或是僅僅了解部分ES6的用法。
由于本項(xiàng)目將會(huì)用到大部分ES6的新特性,為避免將來有小伙伴看不懂還得去查,今天這篇就單獨(dú)為我們之后的開發(fā)做一下ES6的知識(shí)儲(chǔ)備。
用 let / const 來代替 var
因?yàn)?JavaScript 的 var 關(guān)鍵字是聲明全局的變量,所以在 ES6 中引入了兩個(gè)新的變量聲明來解決這個(gè)問題,即 let 和 const 。
它們都用于聲明變量。 區(qū)別在于 const 在聲明后不能改變它的值,而 let 則可以。
和 var 不一樣的是,let 和 const 不存在變量提升。
一個(gè) var 的例子:
var snack = 'Meow Mix';
function getFood(food) {
if (food) {
var snack = 'Friskies';
return snack;
}
return snack;
}
getFood(false); // undefined
使用 let 替換了 var 后的表現(xiàn):
let snack = 'Meow Mix';
function getFood(food) {
if (food) {
let snack = 'Friskies';
return snack;
}
return snack;
}
getFood(false); // 'Meow Mix'
當(dāng)我們重構(gòu)使用 var 的老代碼時(shí),一定要注意這種變化。
盲目使用 let 替換 var 后可能會(huì)導(dǎo)致預(yù)期意外的結(jié)果。
注意:let 和 const 是塊級(jí)作用域語句。所以在語句塊以外引用這些變量時(shí),會(huì)造成引用錯(cuò)誤 ReferenceError。
console.log(x);
let x = 'hi'; // ReferenceError: x is not defined
在本項(xiàng)目中,將使用
let聲明一個(gè)變量,使用const來聲明一個(gè)不可改變的常量。
用塊級(jí)作用域代替 IIFES
我們以往創(chuàng)建一個(gè) 立即執(zhí)行函數(shù) 時(shí),一般是在函數(shù)最外層包裹一層括號(hào)。
ES6支持塊級(jí)作用域,我們現(xiàn)在可以通過創(chuàng)建一個(gè)代碼塊(Block)來實(shí)現(xiàn),不必通過創(chuàng)建一個(gè)函數(shù)來實(shí)現(xiàn),
(function () {
var food = 'Meow Mix';
}());
console.log(food); // Reference Error
使用支持塊級(jí)作用域的ES6的版本:
{
let food = 'Meow Mix';
};
console.log(food); // Reference Error
箭頭函數(shù)
有些時(shí)候,我們?cè)诤瘮?shù)嵌套中需要訪問上下文中的 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 保存在一個(gè)變量里:
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 通過屬性傳進(jìn)去:
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));
};
而如果使用 箭頭函數(shù),this 的值不用我們?cè)僮鋈缟蠋锥未a的特殊處理,直接使用即可。
上面的代碼可以重寫為下面這樣:
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(character => this.name + character);
};
當(dāng)你需要維護(hù)一個(gè) this 上下文的時(shí)候盡量使用 箭頭函數(shù)。
當(dāng)我們編寫只返回一個(gè)表達(dá)式值的簡(jiǎn)單函數(shù)時(shí),也可以使用箭頭函數(shù),如下:
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中,字符串對(duì)象新增了 .includes() 和 .repeat() 方法。
.includes( )
var string = 'food';
var substring = 'foo';
console.log(string.indexOf(substring) > -1);
現(xiàn)在,我們可以使用 .inclues() 方法,替代以往判斷內(nèi)容 > -1 的方式。
.includes() 方法會(huì)極簡(jiǎn)地返回一個(gè)布爾值結(jié)果。
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中,我們可以使用一個(gè)極簡(jiǎn)的方法來實(shí)現(xiàn)重復(fù)字符:
// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'
字符串模版字面量
使用 字符串模板字面量,我可以在字符串中直接使用特殊字符,而不用轉(zhuǎn)義。
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.`;
字符串模板字面量 還支持直接插入變量,可以實(shí)現(xiàn)字符串與變量的直接連接輸出。
var name = 'Tiger';
var age = 13;
console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
更簡(jiǎn)單的版本:
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');
字符串模板字面量 讓我們不必特別關(guān)注多行字符串中的換行轉(zhuǎn)義符號(hào),直接換行即可:
let text = ( `cat
dog
nickelodeon`
);
字符串模板字面量 內(nèi)部可以使用表達(dá)式,像這樣:
let today = new Date();
let text = `The time and date is ${today.toLocaleString()}`;
解構(gòu)
解構(gòu)讓我們可以使用非常便捷的語法,直接將數(shù)組或者對(duì)象中的值直接分別導(dǎo)出到多個(gè)變量中,
解構(gòu)數(shù)組
解構(gòu)數(shù)組
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
解構(gòu)對(duì)象
解構(gòu)對(duì)象
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這樣的庫(kù),
在 Node.js 中,我們則使用 require。
在ES6中,我們現(xiàn)在可以直接使用AMD 和 CommonJS這些模塊了。
使用 CommonJS 的出口
module.exports = 1;
module.exports = { foo: 'bar' };
module.exports = ['foo', 'bar'];
module.exports = function bar () {};
使用 ES6 的出口
在ES6中,提供了多種設(shè)置模塊出口的方式,比如我們要導(dǎo)出一個(gè)變量,那么使用 變量名 :
export let name = 'David';
export let age = 25;??
還可以為對(duì)象 導(dǎo)出一個(gè)列表:
function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
export { sumTwo, sumThree };
我們也可以使用簡(jiǎn)單的一個(gè) export 關(guān)鍵字來導(dǎo)出一個(gè)結(jié)果值:
export function sumTwo(a, b) {
return a + b;
}
export function sumThree(a, b, c) {
return a + b + c;
}
最后,我們可以 導(dǎo)出一個(gè)默認(rèn)出口:
function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
let api = {
sumTwo,
sumThree
};
export default api;
/*
* 與以下的語句是對(duì)等的:
* export { api as default };
*/
實(shí)踐:總是在模塊的 最后 使用
export default方法。
它讓模塊的出口更清晰明了,節(jié)省了閱讀整個(gè)模塊來尋找出口的時(shí)間。
更多的是,在大量CommonJS模塊中,通用的習(xí)慣是設(shè)置一個(gè)出口值或者出口對(duì)象。
堅(jiān)持這個(gè)規(guī)則,可以讓我們的代碼更易讀,且更方便的聯(lián)合使用CommonJS和ES6模塊。
ES6 中的導(dǎo)入
ES6提供了好幾種模塊的導(dǎo)入方式。我們可以單獨(dú)引入一個(gè)文件:
import 'underscore';
這里需要注意的是, 整個(gè)文件的引入方式會(huì)執(zhí)行該文件內(nèi)的最上層代碼。
就像Python一樣,我們還可以命名引用:
import { sumTwo, sumThree } from 'math/addition';
我們甚至可以使用 as 給這些模塊重命名:
import {
sumTwo as addTwoNumbers,
sumThree as sumThreeNumbers
} from 'math/addition';
另外,我們能 引入所有的東西 (也稱為命名空間引入)
import * as util from 'math/addition';
最后,我們能可以從一個(gè)模塊的眾多值中引入一個(gè)列表:
import * as additionUtil from 'math/addtion';
const { sumTwo, sumThree } = additionUtil;
像這樣引用默認(rèn)對(duì)象:
import api from 'math/addition';
// Same as: import { default as api } from 'math/addition';
我們建議一個(gè)模塊導(dǎo)出的值應(yīng)該越簡(jiǎn)潔越好,不過有時(shí)候有必要的話命名引用和默認(rèn)引用可以混著用。如果一個(gè)模塊是這樣導(dǎo)出的:
// foos.js
export { foo as default, foo1, foo2 };
那我們可以如此導(dǎo)入這個(gè)模塊的值:
import foo, { foo1, foo2 } from 'foos';
我們還可以導(dǎo)入commonjs模塊,例如React:
import React from 'react';
const { Component, PropTypes } = React;
更簡(jiǎn)化版本:
import React, { Component, PropTypes } from 'react';
注意:被導(dǎo)出的值是被 綁定的,而不是引用。
所以,改變一個(gè)模塊中的值的話,會(huì)影響其他引用本模塊的代碼,一定要避免此種改動(dòng)發(fā)生。
參數(shù)
在ES5中,許多種方法來處理函數(shù)的 參數(shù)默認(rèn)值(default values),參數(shù)數(shù)量(indefinite arguments),參數(shù)命名(named parameters)。
ES6中,我們可以使用非常簡(jiǎn)潔的語法來處理上面提到的集中情況。
默認(rèn)參數(shù)
function addTwoNumbers(x, y) {
x = x || 0;
y = y || 0;
return x + y;
}
ES6中,我們可以簡(jiǎn)單為函數(shù)參數(shù)啟用默認(rèn)值:
function addTwoNumbers(x=0, y=0) {
return x + y;
}
addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0
rest 參數(shù)
ES5中,遇到參數(shù)數(shù)量不確定時(shí),我們只能如此處理:
function logArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
使用 rest 操作符,我們可以給函數(shù)傳入一個(gè)不確定數(shù)量的參數(shù)列表:
function logArguments(...args) {
for (let arg of args) {
console.log(arg);
}
}
命名參數(shù)
命名函數(shù)
ES5中,當(dāng)我們要處理多個(gè) 命名參數(shù) 時(shí),通常會(huì)傳入一個(gè) 選項(xiàng)對(duì)象 的方式,這種方式被jQuery采用。
function initializeCanvas(options) {
var height = options.height || 600;
var width = options.width || 400;
var lineStroke = options.lineStroke || 'black';
}
我們可以利用上面提到的新特性 解構(gòu) ,來完成與上面同樣功能的函數(shù):
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
如果我們需要把這個(gè)參數(shù)變?yōu)榭蛇x的,那么只要把該參數(shù)解構(gòu)為一個(gè)空對(duì)象就好了:
function initializeCanvas(
{ height=600, width=400, lineStroke='black'} = {}) {
// ...
}
展開操作
我們可以利用展開操作符(Spread Operator)來把一組數(shù)組的值,當(dāng)作參數(shù)傳入:
Math.max(...[-1, 100, 9001, -32]); // 9001
類 Classes
在ES6以前,我們實(shí)現(xiàn)一個(gè)類的功能的話,需要首先創(chuàng)建一個(gè)構(gòu)造函數(shù),然后擴(kuò)展這個(gè)函數(shù)的原型方法,就像這樣:
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提供了一些語法糖來實(shí)現(xiàn)上面的功能,我們可以直接創(chuàng)建一個(gè)類:
class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
incrementAge() {
this.age += 1;
}
}
繼承父類的子類只要簡(jiǎn)單的使用 extends 關(guān)鍵字就可以了:
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);
}
}
實(shí)踐:ES6新的類語法把我們從晦澀難懂的實(shí)現(xiàn)和原型操作中解救出來,這是個(gè)非常適合初學(xué)者的功能,而且能讓我們寫出更干凈整潔的代碼。
Symbols
Symbols在ES6版本之前就已經(jīng)存在了,但現(xiàn)在我們擁有一個(gè)公共的接口來直接使用它們。
Symbols是不可更改的(immutable)并且唯一的(unique),它可用作任何hash數(shù)據(jù)類型中的鍵。
Symbol()
調(diào)用 Symbol() 或者 Symbol(描述文本) 會(huì)創(chuàng)建一個(gè)唯一的、在全局中不可以訪問的Symbol對(duì)象。
一個(gè) Symbol() 的應(yīng)用場(chǎng)景是:在自己的項(xiàng)目中使用第三方代碼庫(kù),且你需要給他們的對(duì)象或者命名空間打補(bǔ)丁代碼,又不想改動(dòng)或升級(jí)第三方原有代碼的時(shí)候。
例如,如果你想給 React.Component 這個(gè)類添加一個(gè) refreshComponent 方法,但又確定不了這個(gè)方法會(huì)不會(huì)在下個(gè)版本中加入,你可以這么做:
const refreshComponent = Symbol();
React.Component.prototype[refreshComponent] = () => {
// do something
}
Symbol.for(key)
使用 Symbol.for(key) 也是會(huì)創(chuàng)建一個(gè)不可改變的Symbol對(duì)象,但區(qū)別于上面的創(chuàng)建方法,這個(gè)對(duì)象是在全局中可以被訪問到的。
兩次相同的 Symbol.for(key) 調(diào)用會(huì)返回相同的Symbol實(shí)例。
提示:這并不同于 Symbol(description)。
Symbol('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol.for('foo') // true
Symbols常用的一個(gè)使用場(chǎng)景,尤其是使用 Symbol.for(key) 方法,是用于實(shí)現(xiàn)代碼間的互操作。
在你的代碼中,通過在包含一些已知接口的第三方庫(kù)的對(duì)象參數(shù)中查找Symbol成員,你可以實(shí)現(xiàn)這種互操作。
例如:
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');
}
}
之后在另一個(gè)庫(kù)中:
const specialRead = Symbol.for('specialRead');
class SomeReadableType {
[specialRead]() {
const reader = createSomeReaderFrom(this);
return reader;
}
}
注意:關(guān)于Symbol互操作的使用,一個(gè)值得一提的例子是
Symbol.iterable。Symbol.iterable存在ES6的所有可枚舉對(duì)象中:數(shù)組(Arrays)、
字符串(strings)、生成器(Generators)等等。當(dāng)它作為一個(gè)方法被調(diào)用時(shí),它將會(huì)返回一個(gè)帶有枚舉接口的對(duì)象。
Maps
Maps 是一個(gè)JavaScript中很重要(迫切需要)的數(shù)據(jù)結(jié)構(gòu)。
在ES6之前,我們創(chuàng)建一個(gè) hash 通常是使用一個(gè)對(duì)象:
var map = new Object();
map[key1] = 'value1';
map[key2] = 'value2';
但是,這樣的代碼無法避免函數(shù)被特別的屬性名覆蓋的意外情況:
> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
> TypeError: Property 'hasOwnProperty' is not a function
Maps 讓我們使用 set,get 和 search 操作數(shù)據(jù)。
let map = new Map();
> map.set('name', 'david');
> map.get('name'); // david
> map.has('name'); // true
Maps最強(qiáng)大的地方在于我們不必只能使用字符串來做key了,現(xiàn)在可以使用任何類型來當(dāng)作key,而且key不會(huì)被強(qiáng)制類型轉(zhuǎn)換為字符串。
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
}
提示:當(dāng)使用
map.get()判斷值是否相等時(shí),非基礎(chǔ)類型比如一個(gè)函數(shù)或者對(duì)象,將不會(huì)正常工作。
有鑒于此,還是建議使用字符串,布爾和數(shù)字類型的數(shù)據(jù)類型。
我們還可以使用 .entries() 方法來遍歷整個(gè)map對(duì)象:
for (let [key, value] of map.entries()) {
console.log(key, value);
}
WeakMaps
在ES5之前的版本,我們?yōu)榱舜鎯?chǔ)私有數(shù)據(jù),有好幾種方法。像使用這種下劃線命名約定:
class Person {
constructor(age) {
this._age = age;
}
_incrementAge() {
this._age += 1;
}
}
在一個(gè)開源項(xiàng)目中,命名規(guī)則很難維持得一直很好,這樣經(jīng)常會(huì)造成一些困擾。
此時(shí),我們可以選擇使用WeakMaps來替代Maps來存儲(chǔ)我們的數(shù)據(jù):
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來保存我們私有數(shù)據(jù)的理由之一是不會(huì)暴露出屬性名,就像下面的例子中的 Reflect.ownKeys():
> const person = new Person(50);
> person.incrementAge(); // 'Midlife crisis'
> Reflect.ownKeys(person); // []
一個(gè)使用WeakMaps存儲(chǔ)數(shù)據(jù)更實(shí)際的例子,是存儲(chǔ)與DOM元素相關(guān)聯(lián)的數(shù)據(jù),而這不會(huì)對(duì)DOM元素本身產(chǎn)生污染:
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
上面的例子中,一旦對(duì)象被垃圾回收器給銷毀了,WeakMaps會(huì)自動(dòng)的把這個(gè)對(duì)象所對(duì)應(yīng)的鍵值對(duì)數(shù)據(jù)同時(shí)銷毀。
提示:結(jié)合這個(gè)例子,再考慮下jQuery是如何實(shí)現(xiàn)緩存帶有引用的DOM元素這個(gè)功能的。使用WeakMaps的話,當(dāng)被緩存的DOM元素被移除的時(shí),jQuery可以自動(dòng)釋放相應(yīng)元素的內(nèi)存。
通常情況下,在涉及DOM元素存儲(chǔ)和緩存的情況下,使用WeakMaps是非常有效的。
Promises
Promises讓我們把多縮進(jìn)難看的代碼(回調(diào)地獄):
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?,F(xiàn)在我們有了原生版本的 Promises:
new Promise((resolve, reject) =>
reject(new Error('Failed to fulfill Promise')))
.catch(reason => console.log(reason));
這里有兩個(gè)處理函數(shù),resolve(當(dāng)Promise執(zhí)行成功完畢時(shí)調(diào)用的回調(diào)函數(shù)) 和 reject (當(dāng)Promise執(zhí)行不接受時(shí)調(diào)用的回調(diào)函數(shù))
Promises的好處:大量嵌套錯(cuò)誤處理回調(diào)函數(shù)會(huì)使代碼變得難以閱讀理解。
使用Promises,我們可以通過清晰的路徑將錯(cuò)誤事件讓上傳遞,并且適當(dāng)?shù)靥幚硭鼈儭?br> 此外,Promise處理后的值,無論是解決(resolved)還是拒絕(rejected)的結(jié)果值,都是不可改變的。
下面是一些使用Promises的實(shí)際例子:
var request = require('request');
return new Promise((resolve, reject) => {
request.get(url, (error, response, body) => {
if (body) {
resolve(JSON.parse(body));
} else {
resolve({});
}
});
});
我們還可以使用 Promise.all() 來 并行化 的處理一組異步的操作。
let urls = [
'/api/commits',
'/api/issues/opened',
'/api/issues/assigned',
'/api/issues/completed',
'/api/issues/comments',
'/api/pullrequests'
];
let promises = urls.map((url) => {
return new Promise((resolve, reject) => {
$.ajax({ url: url })
.done((data) => {
resolve(data);
});
});
});
Promise.all(promises)
.then((results) => {
// Do something with results of all our promises
});
Generators 生成器
就像Promises如何讓我們避免回調(diào)地獄一樣,Generators也可以使我們的代碼扁平化,同時(shí)給予我們開發(fā)者像開發(fā)同步代碼一樣的感覺來寫異步代碼。Generators本質(zhì)上是一種支持的函數(shù),隨后返回表達(dá)式的值。
Generators實(shí)際上是支持暫停運(yùn)行,隨后根據(jù)上一步的返回值再繼續(xù)運(yùn)行的一種函數(shù)。
下面代碼是一個(gè)使用generators函數(shù)的簡(jiǎn)單例子:
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 }
就像上面的例子,當(dāng)next運(yùn)行時(shí),它會(huì)把我們的generator向前“推動(dòng)”,同時(shí)執(zhí)行新的表達(dá)式。
我們能利用Generators來像書寫同步代碼一樣書寫異步代碼。
// Hiding asynchronousity with Generators
function request(url) {
getJSON(url, function(response) {
generator.next(response);
});
}
這里我們寫個(gè)generator函數(shù)將要返回我們的數(shù)據(jù):
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 確實(shí)拿到數(shù)據(jù)并轉(zhuǎn)換后再賦值給 data1。
當(dāng)我們使用generators來像書寫同步代碼一樣書寫我們的異步代碼邏輯時(shí),沒有一種清晰簡(jiǎn)單的方式來處理期間可能會(huì)產(chǎn)生的錯(cuò)誤或者異常。在這種情況下,我們可以在我們的generator中引入Promises來處理,就像下面這樣:
function request(url) {
return new Promise((resolve, reject) => {
getJSON(url, resolve);
});
}
我們?cè)賹懸粋€(gè)函數(shù),其中使用 next 來步進(jìn)我們的generator的同事,再利用我們上面的 request 方法來產(chǎn)生(yield)一個(gè)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 來捕捉和處理錯(cuò)誤了。
使用了我們新版的Generator后,新版的調(diào)用就像老版本一樣簡(jiǎn)單可讀(譯者注:有微調(diào)):
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后,我們可以重用我們的老版本代碼實(shí)現(xiàn),以此展示了Generator的力量。
當(dāng)使用Generators和Promises后,我們可以像書寫同步代碼一樣書寫異步代碼的同時(shí)優(yōu)雅地解決了錯(cuò)誤處理問題。
此后,我們實(shí)際上可以開始利用更簡(jiǎn)單的一種方式了,它就是async-await。
Async Await
async await 給我們提供了一種更輕松的、更簡(jiǎn)單的可以替代的實(shí)現(xiàn)上面 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();
它們看上去和Generators很像。我強(qiáng)烈推薦使用 async await 來替代Generators + Promises的寫法。
Getter/Setter 函數(shù)
ES6 實(shí)現(xiàn)了 getter 和 setter 函數(shù),比如下面這個(gè)例子:
class Employee {
constructor(name) {
this._name = name;
}
get name() {
if(this._name) {
return 'Mr. ' + this._name.toUpperCase();
} else {
return undefined;
}
}
set name(newName) {
if (newName == this._name) {
console.log('I already have this name.');
} else if (newName) {
this._name = newName;
} else {
return false;
}
}
}
var emp = new Employee("James Bond");
if (emp.name) {
console.log(emp.name);
}
emp.name = "Bond 007";
console.log(emp.name);
瀏覽器也在對(duì)象中實(shí)現(xiàn)了 getter 和 setter 函數(shù),我們可以使用它們來實(shí)現(xiàn) 計(jì)算屬性,在設(shè)置和獲取一個(gè)屬性之前加上監(jiān)聽器和處理。
var person = {
firstName: 'James',
lastName: 'Bond',
get fullName() {
console.log('Getting FullName');
return this.firstName + ' ' + this.lastName;
},
set fullName (name) {
console.log('Setting FullName');
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
person.fullName;
person.fullName = 'Bond 007';
person.fullName;
總結(jié)
雖然我們不使用ES6依然能完成整個(gè)項(xiàng)目,但能熟練使用ES6新特性,將會(huì)是代碼更簡(jiǎn)潔優(yōu)雅。
所以,盡快把ES6的知識(shí)掌握了吧。記得點(diǎn)好看呦!
