序: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.
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 秒以後變爲rejected
。p2
的狀態在 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
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成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]);
只要p1
、p2
、p3
之中有一個實例率先改變狀態,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 接口的數據結構以下。
一個對象若是要具有可被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
表達式自己沒有返回值,或者說老是返回undefined
。next
方法能夠帶一個參數,該參數就會被看成上一個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 是單線程語言,只能保持一個調用棧。引入協程之後,每一個任務能夠保持本身的調用棧。這樣作的最大好處,就是拋出錯誤的時候,能夠找到原始的調用棧。不至於像異步操做的回調函數那樣,一旦出錯,原始的調用棧早就結束。
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
而不是Me
,Me
只在 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 模塊自己,由於它不是對象。