es6的初級簡易總結

序:javascript

1.用let const 聲明變量。html

 

2.解構賦值:java

用途:a.交換變量的值;  b.從函數返回多個值;   c.函數參數的定義及默認值;   d.提取JSON數據;   e.遍歷Map;   f.輸入模塊。node

 

3.字符串的擴展:git

a.完善之前超出範圍的字符處理;   b.能夠用for...of循環;    c.includes(),startWith(),endsWith(),repeat(),es6

padStart(),padEnd()等api;   d.模板字符串;  e.標籤模板(是函數調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。做用1:過濾html字符串,防止用戶惡意輸入;2.多語言轉換。github

 

4.正則的擴展:shell

u 修飾符(完善之前超出範圍的字符處理)、y修飾符(粘連;相似g修飾符,確保匹配必須從剩餘的第一個位置開始。)等。編程

 

5.數值的擴展:json

Number.isNaN()  Number.isFinite()(只對數值有效,不轉換) Number.isInteger()(是否爲整數) Number.EPSILON()(設置偏差範圍 好比0.1+0.2不等於0.3) 。Math.trunc()(去除小數部分);Math.sign()(判斷一個數是正負仍是0或NAN);Math.cbrt()(立方根); Math.clz32(); Math.imul();Math.fround();Math.hypot()(全部參數的平方和的平方根) 還有一些對數方法及指數運算符(**)等。

 

6.函數的擴展:

a參數能夠設置默認值;  b.rest參數(...變量名,是一個數組。),用於獲取函數的多餘參數,能夠取代arguments對象。  c.箭頭函數(this是定義時的指向,不是運行時指向。無arguments對象,不能new,不能yield);  d.雙冒號運算符(::用於綁定this);   e.尾調用和尾遞歸的優化(減小調用幀,節省內存。能夠用循環代替遞歸)。

 

7.數組的擴展:

a.擴展運算符(...)是rest參數的逆運算,將數組轉爲逗號分隔的參數序列,主要用於函數調用,可替代apply方法,用途很廣;也能夠將某些數據結構轉爲數組(背後調用的是iterator接口);   b.Array.from:將類數組對象和可遍歷對象轉爲真正的數組。(只要有length屬性均可以轉換);  c.Array.of():將一組值,轉換爲數組;    d.copyWithin()(將指定位置的成員複製到其餘位置,會覆蓋,而後返回當前數組);   e.find()和findIndex()(參數是一個回調函數,返回第一個找到的成員(下標)) ;     f.fill():填充數組,用於初始化數組;   g.keys(),values() entries(),:分別遍歷下標,鍵值,鍵值對。  h.includes():相似字符串的includes。  i.數組的空位(儘可能避免空位

 

8.對象的擴展:

a.簡潔表達法;     b.屬性名錶達式([property])    c.Object.is()(與===的區別爲is的+0不等於 -0,NaN等於自身);    d.Object.assign()(用於合併對象,同名屬性會覆蓋,屬於淺拷貝,即拷貝的是引用。),能夠爲對象添加屬性、方法、克隆對象、合併對象、爲屬性指定默認值等。   e.Object.getOwnPropertyDescriptor(s)獲取屬性的描述對象

f.屬性的遍歷(for...in(遍歷對象自身和繼承的可枚舉屬性)   Object.keys(obj)(返回一個數組,包括對象自身(不含繼承的)可枚舉屬性)   Object.getOwnPropertyNames(obj)  (返回一個數組,包含對象自身的全部屬性,包括不可枚舉的)Object.getOwnPropertySymbols(obj)   Reflect.ownKeys(obj)(包含全部鍵名,包含Symbol,也不管是否可枚舉)

g.__proto__屬性的替代:Object.setPrototypeOf()、Object.getPrototypeOf()、Object.create();

h.super()關鍵字:指向對象的原型對象。

i.Object.keys():返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。

Object.values():方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。

Object.entries():方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。可用於遍歷對象的屬性,和講對象轉爲Map結構。

j. 對象的擴展運算符;     k.    ?. (簡化判斷是否存在的寫法)。

 

Symbol:

獨一無二的值,經過Symbol函數生成;如Symbol.iterator屬性

 

Set和Map結構:

1.Set:

a.相似於數組,可是成員的值都是惟一的,沒有重複的值,能夠去重。   b.屬性:constructor,size;

c.方法:add(value),delete(value),has(value),clear();     d.遍歷:keys(),values(),entries(),forEach();

WeakSet適合臨時存放一組對象,不適合引用,由於會隨時消失;

2.Map:

a.提供(值-值對結構),能夠被for...of遍歷;   b.屬性和方法:size,   set(key,value), get(key), has(key) ,delete(key),

clear();     c.遍歷:同set;

WeakMap 能夠將dom節點做爲鍵名,有助於防止內存泄露。

 

Proxy:能夠在目標對象以前設置一層攔截,對外界的訪問進行過濾和改寫。

 

Reflect:將來從Reflect身上拿語言內部的方法並修正細節。如Object.defineProperty等

 

Promise對象:

異步回調解決的一種方案,Promise實例生成後,能夠用then方法分別指定resolved和rejected狀態的回調函數。Promise.all()將多個Promise實例包裝成一個實例。都完成才完成,一個失敗即失敗;Promise.race()只要有一個實例率先改變狀態,總實例就改變狀態。Promise也能夠結合Generator函數使用。

 

Iterator和for...of: 

a.能夠爲數據(Object Array Map Set)設置Iterator接口(Symbol.iterator),能夠供for...of遍歷。

b.調用Iterator接口的場合:解構,...運算符,yield* 數組,字符串,for...of,Generator,Set,Map,類數組也可for...of;

普通對象須要部署接口或者用Object.keys()或Generator函數從新包裝。

c.for...of的優點:1.沒有for..in的一些缺點(字符串下標,牽扯原型鏈等);2.與forEach比能夠與break,continue,return配合;3.提供遍歷數據統一接口。

 

Generator:

a.異步編程解決方案之一,狀態機及遍歷器對象生成函數。調用後不執行,返回指向內部狀態的遍歷器對象。yield表達式是暫停執行的標記,調用next方法,移動指針至下一步,直到結束或return。

b.next方法能夠帶參數,該參數被當作上一個yield表達式的返回值(不然yield表達式爲undefined

c.能夠將generator函數改形成普通構造函數。

d.generator的異步應用:需配合Thunkify或co模塊。

e.generator函數的語法糖升級版:async函數:將yield改爲await,*號改爲async,並內置執行器,返回promise對象。

 

Class:類。

繼承使用extends關鍵字。在調用super()後纔可使用this關鍵字。

 

Decorator修飾器:

用來修改類的行爲。@  core-decorator第三方庫提供常見修飾器。(@autobind(this綁定原始對象) @readonly(屬性方法不可寫) @override(檢查子類方法是否正確覆蓋父類的同名方法。)等)

 

Module語法:模塊化 import導入 export(default)導出。

CommonJS模塊是運行時加載,ES6模塊是編譯時輸出接口;

CommonJS模塊輸出的是值的拷貝,ES6模塊輸出的是值的引用。

 

 

如下是詳細摘要及總結:

---------------------------------------------------------------------------------------------------------------------------------------------------------------

1.let const聲明變量

1.let:不存在變量提高,變量須要聲明後使用,不容許重複聲明,適用for循環等。

2.const:const保證的不是變量的值不得改動,而是變量指向的內存地址不得改動,(例如能夠爲對象添加屬性,不能將對象指向別的對象。)適用於模塊內聲明變量等。

3.es6聲明變量的六種方法 var function let const import class.

 

2.解構賦值。(一些栗子)

 1 let [a, [b], d] = [1, [2, 3], 4];
 2 a // 1
 3 b // 2
 4 d // 4
 5 
 6 let [x = 1] = [undefined];
 7 x // 1
 8 
 9 let [x = 1] = [null];
10 x // null
11 
12 
13 //用變量使用Math的方法
14 let { log, sin, cos } = Math; 
15 
16 
17 //因爲數組本質是特殊的對象,因此能夠對數組進行對象屬性的解構
18 let arr = [1, 2, 3];
19 let {0 : first, [arr.length - 1] : last} = arr;
20 first // 1
21 last // 3
22 
23 
24 //函數參數的解構也可使用默認值。
25 function move({x = 0, y = 0} = {}) {
26   return [x, y];
27 }
28 
29 move({x: 3, y: 8}); // [3, 8]
30 move({x: 3}); // [3, 0]
31 move({}); // [0, 0]
32 move(); // [0, 0
33 
34 //如下是一些用途:
35 //交換數值
36 let x = 1;
37 let y = 2;
38 
39 [x, y] = [y, x];
40 
41 
42 // 返回一個數組
43 
44 function example() {
45   return [1, 2, 3];
46 }
47 let [a, b, c] = example();
48 
49 // 返回一個對象
50 
51 function example() {
52   return {
53     foo: 1,
54     bar: 2
55   };
56 }
57 let { foo, bar } = example();
58 
59 //將一組參數與變量名對應起來。
60 function f([x, y, z]) { ... }
61 f([1, 2, 3]);
62 
63 // 參數是一組無次序的值
64 function f({x, y, z}) { ... }
65 f({z: 3, y: 2, x: 1});
66 
67 //提取json數據頗有效
68 let jsonData = {
69   id: 42,
70   status: "OK",
71   data: [867, 5309]
72 };
73 
74 let { id, status, data: number } = jsonData;
75 
76 console.log(id, status, number);
77 // 42, "OK", [867, 5309]
78 
79 //輸入模塊的指定方法
80 const { SourceMapConsumer, SourceNode } = require("source-map");

 

3.字符串的擴展。

1.可使用for...of遍歷字符串  ;

2.新方法:includes(),startsWith(),endsWith(),repeat()        padstart(),padEnd() (補全字符串);

3.模板字符串  ` ......${a}......`   (很是實用);

 

4.函數的擴展。

1.函數可使用默認參數,自動聲明,不能用let或const再次聲明,參數默認值是惰性求值(每次調用函數都會從新計算).

 

//若參數中沒有={}則foo()會報錯
function foo({x, y = 5}={}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // undefined 5

 

指定默認值後,函數的length屬性返回沒有指定默認值的參數的個數。

能夠利用參數默認值,指定某個參數不能省略,若是省略就拋出錯誤,以下:

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter

參數的默認值不是在定義時執行,而是在運行時執行,若將默認值設爲undefined,代表這個參數能夠省略。

 

2.用rest參數(...)取代arguments對象.

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10



// arguments變量的寫法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest參數的寫法
const sortNumbers = (...numbers) => numbers.sort();

3.箭頭函數.

var f = () => 5;
// 等同於
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
  return num1 + num2;
};


var sum = (num1, num2) => { return num1 + num2; }


// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

//只有一行且不須要返回值時能夠省略大括號
let fn = () => void doesNotReturn();

//表達更簡潔
const isEven = n => n % 2 == 0;
const square = n => n * n;

// 正常函數寫法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭頭函數寫法
var result = values.sort((a, b) => a - b);

// 正常函數寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數寫法
[1,2,3].map(x => x * x);

注意點:

函數體內的this對象是定義時所在的對象,而不是使用時所在的對象。(不能使用new),沒有arguments對象(用rest...代替),

不能用做Generator(生成器)函數.

 

 

//this區別栗子  箭頭函數的this綁定Timer
function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭頭函數
  setInterval(() => this.s1++, 1000);
  // 普通函數
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0



var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};
/*上面代碼的init方法中,使用了箭頭函數,這致使這個箭頭函數裏面的this,老是指向handler對象。不然,回調函數運行時,this.doSomething這一行會報錯,由於此時this指向document對象。*/

 

深究:其實箭頭函數沒有本身的this.因此使用的是外層的this,因此不能用call apply bind方法.(能夠用::替代)

4.尾遞歸優化:將遞歸改寫爲循環;

5.尾調用優化:只保留內層函數的調用幀(簡寫)

 

 5.數組的擴展。

1.擴展運算符(...) 是rest參數的逆運算。將數組轉爲逗號分隔的參數序列。

即:三個點(...)在函數參數裏使用時,表明一個數組;在函數調用的()裏使用時,表明參數序列。

能夠替代數組的apply方法,以下:

// ES5 的寫法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的寫法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

擴展運算符的應用:

a,複製數組 例如:   let a2=[...a1] ;

b.合併數組 例如:  [...arr1,...arr2,...arr3]  相似es5的concat方法;

c.與解構賦值結合  例如:[a,...rest]=list;

d.能夠將字符串轉爲真正的數組 例如:[...'hello']  //["h","e","l","l","o"];

e.將實現了Iterator的類數組轉爲數組  例如[...nodelist] (例外:若類數組無Iterator接口能夠用Array.from轉化成數組,以下:)

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
//可使用Array.from(arrayLike)

f.能夠用於Map、Set結構和Generator函數

 

2.Array.from() :將類數組對象和可遍歷的對象轉爲數組 ,只要有length屬性便可(如Array.from{length:3} //[undefined*3])

以下:(es5的替代方法爲Array.prototype.slice)

// NodeList對象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
  console.log(p);
});

// arguments對象
function foo() {
  var args = Array.from(arguments);
  // ...
}

Array.from還接受第二個參數,能夠對每一個元素進行處理,將處理後的值放入返回的數組。

Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

擴展應用:

Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

以上代碼的第一個參數指定了第二個參數運行的次數,很靈活。

 

3.Array.of():能夠替代Array()方法,行爲更統一。   (實現爲return [].slice.call(arguments);

4.copyWithin() :將指定位置的成員複製到其餘位置(會覆蓋),而後返回當前數組。

5.數組實例的find()和findIndex()方法:

find():找出第一個符合條件的數組成員(第一個爲true的成員),若沒有,返回undefined;

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

findIndex():找出第一個符合條件的數組成員的位置,若沒有,返回-1;

這兩個方法均可以接受第二個參數,綁定回調函數的this對象。

6.fill()方法:填充數組,以下:

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
//三個參數分別是填充的值,開始位置和結束位置

7.數組實例的entries(),keys()和values():用於for...of循環遍歷。

8.includes():相似字符串的includes方法,比indexOf()更語義化一些,也不會對NaN形成誤判。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

9:數組的空位:es6將空位轉爲undefined , es5則比較混亂。

 6.對象的擴展。

1.屬性/方法名的簡寫:注意:簡寫的屬性/方法名是字符串,不屬於關鍵字。(能夠用class()等)

2.屬性名錶達式:能夠在對象中將表達式放在方括號裏,注意:若是屬性名錶達式是一個對象,會將對象轉爲字符串[object object],須要注意。以下:

 

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

 

3.Object.is()   相似===    (不一樣之處 +0不等於-0,NaN等於自身)

4.Object.assign()  拷貝(屬性相同會覆蓋,只拷貝能夠枚舉的屬性,不拷貝繼承屬性)

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

以上,除了字符串以數組形式拷貝入目標對象,其餘無效果。

注意點:

a.Object.assign()是淺拷貝,若是源對象某個屬性的值是對象,那麼拷貝獲得的是它的引用,互相影響;

b.同名屬性的替換;

c.若用來處理數組,會把數組視爲對象;

常見用途:

a.爲對象加屬性/方法,以下:

//添加屬性
class Point { constructor(x, y) { Object.assign(
this, {x, y}); } } //添加方法 Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同於下面的寫法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };

b.克隆對象,以下:

function clone(origin) {
  return Object.assign({}, origin);
}

//若想保持繼承鏈,能夠以下:
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

c.合併對象,以下:

const merge =
  (...sources) => Object.assign({}, ...sources);

5.屬性的可枚舉性和遍歷:可使用Object.getOwnPropertyDescriptor(obj,'name')獲取屬性的描述對象。

有四個操做會忽略enumerable爲false的屬性:for ..in  Object.keys()   JSON.stringify()  Object.assign()。

其中只有for...in會返回繼承的屬性。能夠用Object.keys()代替for...in;

6.Object.getOwnPropertyDescriptors返回指定對象全部自身屬性(非繼承屬性)的描述對象;

7.__proto__:內部屬性,能夠用Object.setPrototypeOf()   Object.getPrototypeOf()   Object.create()代替。

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40
function Rectangle() {
  // ...
}

const rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

8.super關鍵字(注意:目前,只有對象方法的簡寫法可讓 JavaScript 引擎確認,定義的是對象的方法。)

它指向當前對象的原型對象。

9.Object.keys()  Object.values() Object.entries()

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

10.對象的擴展運算符

a.解構賦值:淺拷貝(如有負荷類型的值則拷貝引用,會互相影響,且不能複製繼承自原型的屬性(普通解構賦值能夠繼承到))

b.擴展運算符能夠合併兩個對象

let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);

11.Null傳導運算符:判斷對象是否存在時的簡寫。  如:message?.body?.user?.firstname||'default'

 7.新的原始數據類型Symbol

表示獨一無二的值,能夠用Object.getOwnPropertySymbols獲取到,不會被普通方法遍歷到,因此能夠定義一些內部的變量等。

8.Set和Map結構

能夠用[...new Set(array)]去除數組的重複成員(或者Array.from(new Set(array)))

Set結構的屬性:.size  .constructor  方法:add(value) delete(value) has(value) clear()

// 對象的寫法
const properties = {
  'width': 1,
  'height': 1
};

if (properties[someName]) {
  // do something
}

// Set的寫法
const properties = new Set();

properties.add('width');
properties.add('height');

if (properties.has(someName)) {
  // do something
}

Set的遍歷操做:keys() values() entries() forEach()  注意點:Set的遍歷順序就是插入順序。

Map結構是一種值-值對的數據結構   屬性/方法:size   set(key,value)  get(key) has(key) delete(key) clear()

WeakSet/WeakMap使用場景 :在DOM元素上添加數據時,能夠不用手動刪除引用(避免內存泄漏)。以下:

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];
// 不須要 e1 和 e2 的時候
// 必須手動刪除引用
arr [0] = null;
arr [1] = null;

//若使用WeakMap()能夠不手動釋放對象。

9.Promise對象(用來傳遞異步操做的數據(消息))

缺點:

有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。

Promise也有一些缺點。首先,沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。其次,若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

若是某些事件不斷地反覆發生,通常來講,使用Stream模式是比部署Promise更好的選擇。

 

基本用法:

a.Promise 新建後當即執行,而後,then方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行。

下面是一個用Promise對象實現的 Ajax 操做的例子。

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯了', error);
});

b.resolve函數的參數能夠是另外一個Promise實力,以下:

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

上面代碼中,p1是一個 Promise,3 秒以後變爲rejectedp2的狀態在 1 秒以後改變,resolve方法返回的是p1。因爲p2返回的是另外一個 Promise,致使p2本身的狀態無效了,由p1的狀態決定p2的狀態。因此,後面的then語句都變成針對p1。又過了 2 秒,p1變爲rejected,致使觸發catch方法指定的回調函數。

c.良好習慣:在resolve和reject前加上return,後續操做放在then方法裏。

 

Promise.prototype.then():

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)//至關於es5的return getJSON(...)
).then(
  comments => console.log("resolved: ", comments),//resolve時
  err => console.log("rejected: ", err)//reject時
);

Promise.prototype.catch():

Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

通常來講,不要在then方法裏面定義 Reject 狀態的回調函數(即then的第二個參數),老是使用catch方法。

通常老是建議,Promise 對象後面要跟catch方法,這樣能夠處理 Promise 內部發生的錯誤。catch方法返回的仍是一個 Promise 對象,所以後面還能夠接着調用then方法。Promise 在resolve語句後面,再拋出錯誤,不會被捕獲,等於沒有拋出。由於 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。

Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,由於x沒有聲明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會報錯,由於 y 沒有聲明
  y + 2;
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]

上面代碼中,catch方法拋出一個錯誤,由於後面沒有別的catch方法了,致使這個錯誤不會被捕獲,也不會傳遞到外層.

能夠改寫以下:

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會報錯,由於y沒有聲明
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

Promise.all():

Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。(const p = Promise.all([p1, p2, p3]);

p的狀態由p1p2p3決定,分紅兩種狀況。

(1)只有p1p2p3的狀態都變成fulfilledp的狀態纔會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。例子以下:

const databasePromise = connectDatabase();

const booksPromise = databasePromise
  .then(findAllBooks);

const userPromise = databasePromise
  .then(getCurrentUser);

Promise.all([
  booksPromise,
  userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));

 

注意,若是做爲參數的 Promise 實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()catch方法。若是p2沒有本身的catch方法,就會調用Promise.all()catch方法。

 

Promise.race():

const p = Promise.race([p1, p2, p3]);
只要p1p2p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
例子:
p1p2p3pp
const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

若是 5 秒以內fetch方法沒法返回結果,變量p的狀態就會變爲rejected,從而觸發catch方法指定的回調函數。


Promise.resolve():將現有對象轉爲 Promise 對象.
當參數是一個thenable對象時,以下:
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。

注意點:當即resolve的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。
resolve
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

 

Promise.reject():

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

 

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
注意,Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。

能夠本身部署的方法:
done():用於代碼最後捕捉錯誤。
實現以下:
Promise.reject(reason)rejectedPromise.reject()rejectPromise.resolve
Promise.prototype.done = function (onFulfilled, onRejected) {
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      // 拋出一個全局錯誤
      setTimeout(() => { throw reason }, 0);
    });
};

finally():finally方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。它與done方法的最大區別,它接受一個普通的回調函數做爲參數,該函數無論怎樣都必須執行。

server.listen(0)
  .then(function () {
    // run test
  })
  .finally(server.stop);
//最後用finally關掉服務器,不管結果如何

實現以下:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

Promise.try():讓同步函數同步執行,異步函數異步執行,而且讓它們具備統一的 API

第一種寫法:

(async () => f())()
.then(...)
.catch(...)

第二種寫法:

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next

用Promise.try(),以下:

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

因爲Promise.try爲全部操做提供了統一的處理機制,因此若是想用then方法管理流程,最好都用Promise.try包裝一下。這樣有不少好處,其中一點就是能夠更好地管理異常。

 

 10.Iterator(遍歷器)和for...of

1.a.任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。主要供for...of消費。

ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷的」(iterable)。

例如:
const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

b.原生具有 Iterator 接口的數據結構以下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象
一個對象若是要具有可被for...of循環調用的 Iterator 接口,就必須在Symbol.iterator的屬性上部署(也能夠修改)遍歷器生成方法。(或者使用Map結構)

let it=something[Symbol.iterator]() it.next()...

c.遍歷器對象也能夠部署return()和throw()方法,以下:
for...ofSymbol.iterator
function readLinesSync(file) {
  return {
    next() {
      return { done: false };
    },
    return() {
      file.close();
      return { done: true };
    },
  };
}
// 狀況一
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

// 狀況二
for (let line of readLinesSync(fileName)) {
  console.log(line);
  continue;
}

// 狀況三
for (let line of readLinesSync(fileName)) {
  console.log(line);
  throw new Error();
}
//3種狀況都會觸發return

上面代碼中,狀況一輸出文件的第一行之後,就會執行return方法,關閉這個文件;狀況二輸出全部行之後,執行return方法,關閉該文件;狀況三會在執行return方法關閉文件以後,再拋出錯誤。注意,return方法必須返回一個對象,這是 Generator 規格決定的.

2.for...of(內部調用Symbol.iterator方法)
for...of循環可使用的範圍包括數組、Set 和 Map 結構、某些相似數組的對象(好比arguments對象、DOM NodeList 對象)、 Generator 對象,以及字符串。
for...in循環讀取鍵名,for...of循環讀取鍵值。若是要經過for...of循環,獲取數組的索引,能夠藉助數組實例的entries方法和keys方法

對象:對於普通的對象,for...of結構不能直接使用,會報錯,必須部署了 Iterator 接口後才能使用。可是,這樣狀況下,for...in循環依然能夠用來遍歷鍵名。
解決方案:
for...ofargumentsfor...infor...offor...ofentrieskeysfor...offor...in
for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}

與其餘遍歷語法的比較:

for循環比較麻煩。forEach方法沒法中途跳出。break return都無效。for...in的缺點:

  • 數組的鍵名是數字,可是for...in循環是以字符串做爲鍵名「0」、「1」、「2」等等。
  • for...in循環不只遍歷數字鍵名,還會遍歷手動添加的其餘鍵,甚至包括原型鏈上的鍵。
  • 某些狀況下,for...in循環會以任意順序遍歷鍵名。
  • for...in循環主要是爲遍歷對象而設計的,不適用於遍歷數組。

for...of能夠與break continue return配合使用。

 11.Generator函數
1.a.Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。

調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象(遍歷器對象)

每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

b.yield表達式

遍歷器對象的next方法的運行邏輯以下。

(1)遇到yield表達式,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。

(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。

(3)若是沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值。

(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined

yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面。

c.與Iterator接口的關係:

能夠把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具備 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

二、next方法的參數

yield表達式自己沒有返回值,或者說老是返回undefinednext方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

3.for...of循環

for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,且此時再也不須要調用next方法。(不包括return的值)

原生的 JavaScript 對象沒有遍歷接口,沒法使用for...of循環,經過 Generator 函數爲它加上這個接口,就能夠用了。以下:

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

除了for...of循環之外,擴展運算符(...)、解構賦值和Array.from方法內部調用的,都是遍歷器接口。這意味着,它們均可以將 Generator 函數返回的 Iterator 對象,做爲參數。

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 擴展運算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解構賦值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循環
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

4.Generator.prototype.throw():

Generator 函數返回的遍歷器對象,都有一個throw方法,能夠在函數體外拋出錯誤,而後在 Generator 函數體內捕獲。

throw方法能夠接受一個參數,該參數會被catch語句接收,建議拋出Error對象的實例。

不要混淆遍歷器對象的throw方法和全局的throw命令。上面代碼的錯誤,是用遍歷器對象的throw方法拋出的,而不是用throw命令拋出的。後者只能被函數體外的catch語句捕獲。

var g = function* () {
  while (true) {
    try {
      yield;
    } catch (e) {
      if (e != 'a') throw e;
      console.log('內部捕獲', e);
    }
  }
};

var i = g();
i.next();

try {
  throw new Error('a');
  throw new Error('b');
} catch (e) {
  console.log('外部捕獲', e);
}
// 外部捕獲 [Error: a]

throw方法被捕獲之後,會附帶執行下一條yield表達式。也就是說,會附帶執行一次next方法。

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

上面代碼中,g.throw方法被捕獲之後,自動執行了一次next方法,因此會打印b。另外,也能夠看到,只要 Generator 函數內部部署了try...catch代碼塊,那麼遍歷器的throw方法拋出的錯誤,不影響下一次遍歷。

這種函數體內捕獲錯誤的機制,大大方便了對錯誤的處理。多個yield表達式,能夠只用一個try...catch代碼塊來捕獲錯誤。若是使用回調函數的寫法,想要捕獲多個錯誤,就不得不爲每一個函數內部寫一個錯誤處理語句,如今只在 Generator 函數內部寫一次catch語句就能夠了。

5.Generator.prototype.return():

Generator 函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷 Generator 函數。

若是 Generator 函數內部有try...finally代碼塊,那麼return方法會推遲到finally代碼塊執行完再執行。

6.yield*表達式:

yield*表達式,用來在一個 Generator 函數裏面執行另外一個 Generator 函數。

yield*後面的 Generator 函數(沒有return語句時),等同於在 Generator 函數內部,部署一個for...of循環。

function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}

// 等同於

function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

上面代碼說明,yield*後面的 Generator 函數(沒有return語句時),不過是for...of的一種簡寫形式,徹底能夠用後者替代前者。反之,在有return語句時,則須要用var value = yield* iterator的形式獲取return語句的值。

任何數據結構只要有 Iterator 接口,就能夠被yield*遍歷。

yield*命令能夠很方便地取出嵌套數組的全部成員。

function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e

7.做爲對象屬性的Generator函數:

let obj = {
  * myGeneratorMethod() {
    ···
  }
};
//等同於
let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

8. Generator函數的this

Generator 函數老是返回一個遍歷器,ES6 規定這個遍歷器是 Generator 函數的實例,也繼承了 Generator 函數的prototype對象上的方法。

function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'

若是把g看成普通的構造函數,並不會生效,由於g返回的老是遍歷器對象,而不是this對象。也不能跟new命令一塊兒用,會報錯。因而能夠

將Generator 函數改形成構造函數:

function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() {
  return gen.call(gen.prototype);
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

9.含義:

a.Generator 是實現狀態機的最佳結構

var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick!');
  else
    console.log('Tock!');
  ticking = !ticking;
}//es5

var clock = function* () {
  while (true) {
    console.log('Tick!');
    yield;
    console.log('Tock!');
    yield;
  }
};//es6

上面的 Generator 實現與 ES5 實現對比,能夠看到少了用來保存狀態的外部變量ticking,這樣就更簡潔,更安全(狀態不會被非法篡改)、更符合函數式編程的思想,在寫法上也更優雅。Generator 之因此能夠不用外部變量保存狀態,是由於它自己就包含了一個狀態信息,即目前是否處於暫停態。

b.Generator與協程:

多個線程(單線程狀況下,即多個函數)能夠並行執行,可是隻有一個線程(或函數)處於正在運行的狀態,其餘線程(或函數)都處於暫停態(suspended),線程(或函數)之間能夠交換執行權。也就是說,一個線程(或函數)執行到一半,能夠暫停執行,將執行權交給另外一個線程(或函數),等到稍後收回執行權的時候,再恢復執行。這種能夠並行執行、交換執行權的線程(或函數),就稱爲協程。

從實現上看,在內存中,子例程只使用一個棧(stack),而協程是同時存在多個棧,但只有一個棧是在運行狀態,也就是說,協程是以多佔用內存爲代價,實現多任務的並行。

因爲 JavaScript 是單線程語言,只能保持一個調用棧。引入協程之後,每一個任務能夠保持本身的調用棧。這樣作的最大好處,就是拋出錯誤的時候,能夠找到原始的調用棧。不至於像異步操做的回調函數那樣,一旦出錯,原始的調用棧早就結束。

Generator 與上下文

JavaScript 代碼運行時,會產生一個全局的上下文環境(context,又稱運行環境),包含了當前全部的變量和對象。而後,執行函數(或塊級代碼)的時候,又會在當前上下文環境的上層,產生一個函數運行的上下文,變成當前(active)的上下文,由此造成一個上下文環境的堆棧(context stack)。

這個堆棧是「後進先出」的數據結構,最後產生的上下文環境首先執行完成,退出堆棧,而後再執行完成它下層的上下文,直至全部代碼執行完成,堆棧清空。

Generator 函數不是這樣,它執行產生的上下文環境,一旦遇到yield命令,就會暫時退出堆棧,可是並不消失,裏面的全部變量和對象會凍結在當前狀態。等到對它執行next命令時,這個上下文環境又會從新加入調用棧,凍結的變量和對象恢復執行。

function *gen() {
  yield 1;
  return 2;
}

let g = gen();

console.log(
  g.next().value,
  g.next().value,
);

上面代碼中,第一次執行g.next()時,Generator 函數gen的上下文會加入堆棧,即開始運行gen內部的代碼。等遇到yield 1時,gen上下文退出堆棧,內部狀態凍結。第二次執行g.next()時,gen上下文從新加入堆棧,變成當前的上下文,從新恢復執行。

10.應用:

a異步操做的同步化表達:

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加載UI
loader.next()

// 卸載UI
loader.next()

上面代碼中,第一次調用loadUI函數時,該函數不會執行,僅返回一個遍歷器。下一次對該遍歷器調用next方法,則會顯示Loading界面(showLoadingScreen),而且異步加載數據(loadUIDataAsynchronously)。等到數據加載完成,再一次使用next方法,則會隱藏Loading界面。能夠看到,這種寫法的好處是全部Loading界面的邏輯,都被封裝在一個函數,循序漸進很是清晰。

b.控制流管理:

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

而後,使用一個函數,按次序自動執行全部步驟。

scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 若是Generator函數未結束,就繼續調用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}

注意,上面這種作法,只適合同步操做,即全部的task都必須是同步的,不能有異步操做。由於這裏的代碼一獲得返回值,就繼續往下執行,沒有判斷異步操做什麼時候完成。

利用for...of循環會自動依次執行yield命令的特性,提供一種更通常的控制流管理的方法。

let steps = [step1Func, step2Func, step3Func];

function *iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yield step();
  }
}

將任務分解成步驟以後,還能夠將項目分解成多個依次執行的任務。

let jobs = [job1, job2, job3];

function* iterateJobs(jobs){
  for (var i=0; i< jobs.length; i++){
    var job = jobs[i];
    yield* iterateSteps(job.steps);
  }
}

上面代碼中,數組jobs封裝了一個項目的多個任務,Generator 函數iterateJobs則是依次爲這些任務加上yield*命令。

最後,就能夠用for...of循環一次性依次執行全部任務的全部步驟。

for (var step of iterateJobs(jobs)){
  console.log(step.id);
}

c.部署Iterator接口:

利用 Generator 函數,能夠在任意對象上部署 Iterator 接口。

d.做爲數據結構:

它能夠對任意表達式,提供相似數組的接口。(能夠用for...of遍歷)

 

11.Generator函數的異步應用:

Generator 函數能夠暫停執行和恢復執行,這是它能封裝異步任務的根本緣由。除此以外,它還有兩個特性,使它能夠做爲異步編程的完整解決方案:函數體內外的數據交換和錯誤處理機制。

Thunk函數:

編譯器的「傳名調用」實現,每每是將參數放到一個臨時函數之中,再將這個臨時函數傳入函數體。這個臨時函數就叫作 Thunk 函數。

function f(m) {
  return m * 2;
}

f(x + 5);

// 等同於

var thunk = function () {
  return x + 5;
};

function f(thunk) {
  return thunk() * 2;
}

JavaScript 語言是傳值調用,它的 Thunk 函數含義有所不一樣。在 JavaScript 語言中,Thunk 函數替換的不是表達式,而是多參數函數,將其替換成一個只接受回調函數做爲參數的單參數函數。

Thunkify模塊:生產環境的轉換器,建議使用 Thunkify 模塊。

Thunk 函數如今能夠用於 Generator 函數的自動流程管理。(能夠自動執行 Generator 函數)

co模塊:

co 模塊其實就是將兩種自動執行器(Thunk 函數和 Promise 對象),包裝成一個模塊。使用 co 的前提條件是,Generator 函數的yield命令後面,只能是 Thunk 函數或 Promise 對象。若是數組或對象的成員,所有都是 Promise 對象,也可使用 co。

co 支持併發的異步操做,即容許某些操做同時進行,等到它們所有完成,才進行下一步。

這時,要把併發的操做都放在數組或對象裏面,跟在yield語句後面。

// 數組的寫法
co(function* () {
  var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(res);
}).catch(onerror);

// 對象的寫法
co(function* () {
  var res = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2),
  };
  console.log(res);
}).catch(onerror);
co(function* () {
  var values = [n1, n2, n3];
  yield values.map(somethingAsync);
});

function* somethingAsync(x) {
  // do something async
  return y
}

上面的代碼容許併發三個somethingAsync異步操做,等到它們所有完成,纔會進行下一步。

12.Async函數

 

//一個Generator函數 依次讀取兩個文件
const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

 

寫成async函數,就是下面這樣。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

async函數對 Generator 函數的改進,體如今如下四點。

(1)內置執行器。

async函數的執行,與普通函數如出一轍,只要一行。(例如:asyncReadFile();

它就會自動執行,輸出最後結果。這徹底不像 Generator 函數,須要調用next方法,或者用co模塊,才能真正執行,獲得最後結果。

(2)更好的語義。

(3)更廣的適用性。

co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。

(4)返回值是 Promise。

async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。

進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

 

 

基本用法:

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,代表該函數內部有異步操做。調用該函數時,會當即返回一個Promise對象。

//async函數的多重使用形式。
// 函數聲明
async function foo() {}

// 函數表達式
const foo = async function () {};

// 對象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭頭函數
const foo = async () => {};

 

async函數返回一個 Promise 對象。

async函數內部return語句返回的值,會成爲then方法回調函數的參數。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

async函數內部拋出錯誤,會致使返回的 Promise 對象變爲reject狀態。拋出的錯誤對象會被catch方法回調函數接收到。

async function f() {
  throw new Error('出錯了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出錯了

async函數返回的 Promise 對象,必須等到內部全部await命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

上面代碼中,函數getTitle內部有三個操做:抓取網頁、取出文本、匹配頁面標題。只有這三個操做所有完成,纔會執行then方法裏面的console.log

 

await命令:

正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。

await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。

只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。

有時,咱們但願即便前一個異步操做失敗,也不要中斷後面的異步操做。這時能夠將第一個await放在try...catch結構裏面,這樣無論這個異步操做是否成功,第二個await都會執行。

async function f() {
  try {
    await Promise.reject('出錯了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另外一種方法是await後面的 Promise 對象再跟一個catch方法,處理前面可能出現的錯誤。

async function f() {
  await Promise.reject('出錯了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出錯了
// hello world

若是await後面的異步操做出錯,那麼等同於async函數返回的 Promise 對象被reject。防止出錯的方法,也是將其放在try...catch代碼塊之中。

若是有多個await命令,能夠統一放在try...catch結構中。

下面的例子使用try...catch結構,實現屢次重複嘗試。

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}

test();

 

使用注意點:

1.await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中。

2.將繼發寫成同時觸發:

//繼發
let foo = await getFoo();
let bar = await getBar();

/同時觸發
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3.await命令只能用在async函數之中,若是用在普通函數,就會報錯。

若是確實但願多個請求併發執行,可使用Promise.all方法。當三個請求都會resolved時,下面兩種寫法效果相同。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

一個栗子:

假定某個 DOM 元素上面,部署了一系列的動畫,前一個動畫結束,才能開始後一個。若是當中有一個動畫出錯,就再也不往下執行,返回上一個成功執行的動畫的返回值。

async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略錯誤,繼續執行 */
  }
  return ret;
}

另外一個栗子:

async function logInOrder(urls) {
  // 併發讀取遠程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序輸出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

雖然map方法的參數是async函數,但它是併發執行的,由於只有async函數內部是繼發執行,外部不受影響。後面的for..of循環內部使用了await,所以實現了按順序輸出。

異步遍歷器:

對象的異步遍歷器接口,部署在Symbol.asyncIterator屬性上面。無論是什麼樣的對象,只要它的Symbol.asyncIterator屬性有值,就表示應該對它進行異步遍歷。

for...of循環用於遍歷同步的 Iterator 接口。新引入的for await...of循環,則是用於遍歷異步的 Iterator 接口。

異步遍歷器的設計目的之一,就是 Generator 函數處理同步操做和異步操做時,可以使用同一套接口。

13.Class

 構造函數的prototype屬性,在 ES6 的「類」上面繼續存在。事實上,類的全部方法都定義在類的prototype屬性上面。

class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同於

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

類的內部全部定義的方法,都是不可枚舉的(non-enumerable)。這一點與 ES5 的行爲不一致。

Object.assign方法能夠很方便地一次向類添加多個方法。

class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

與函數同樣,類也可使用表達式的形式定義。

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not defined
 

上面代碼使用表達式定義了一個類。須要注意的是,這個類的名字是MyClass而不是MeMe只在 Class 的內部代碼可用,指代當前類。

若是類的內部沒用到的話,能夠省略Me,也就是能夠寫成下面的形式。

採用 Class 表達式,能夠寫出當即執行的 Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('張三');

person.sayName(); // "張三"

類不存在變量提高(hoist),這一點與 ES5 徹底不一樣。

私有方法:一種作法是在命名上加以區別。(_methods)

另外一種方法就是索性將私有方法移出模塊,由於模塊內部的全部方法都是對外可見的。

class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // baz變成當前函數的私有方法
}

function bar(baz) {
  return this.snaf = baz;
}

 

私有屬性:(#x)

 

this的指向問題:

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}
class Logger {
  constructor() {
    this.printName = (name = 'there') => {
      this.print(`Hello ${name}`);
    };
  }

  // ...
}

 

class的靜態方法:

類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

父類的靜態方法,能夠被子類繼承。靜態方法也是能夠從super對象上調用的。

 

class的靜態屬性和實例屬性:

 
 

class
Foo { } Foo.prop = 1; Foo.prop // 1
// 老寫法
// 新寫法 class Foo { static prop = 1; }
 

 

ES6 爲new命令引入了一個new.target屬性,該屬性通常用在構造函數之中,返回new命令做用於的那個構造函數。若是構造函數不是經過new命令調用的,new.target會返回undefined。

 

class的繼承:

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調用父類的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調用父類的toString()
  }
}

上面代碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。

子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。

ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this

Object.getPrototypeOf方法能夠用來從子類上獲取父類。Object.getPrototypeOf(ColorPoint) === Point,可使用這個方法判斷,一個類是否繼承了另外一個類。

 

super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。在這兩種狀況下,它的用法徹底不一樣。

大多數瀏覽器的 ES5 實現之中,每個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。

(1)子類的__proto__屬性,表示構造函數的繼承,老是指向父類。

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性。

Mixin 指的是多個對象合成一個新的對象,新對象具備各個組成成員的接口。它的最簡單實現以下。

14.Moduel

ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。(運行時加載)

ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。

  • CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
  • CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
相關文章
相關標籤/搜索