摘要:去除數(shù)組的重復(fù)成員這表明,在內(nèi)部,兩個是相等。返回一個布爾值,表示該值是否為的成員。使用回調(diào)函數(shù)遍歷每個成員沒有返回值。對象特點(diǎn)對象有三種狀態(tài)進(jìn)行中已完成,又稱和已失敗。方法是的別名,用于指定發(fā)生錯誤時(shí)的回調(diào)函數(shù)。
Set和Map數(shù)據(jù)結(jié)構(gòu) Set
新的數(shù)據(jù)結(jié)構(gòu)Set類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。Set 本身是一個構(gòu)造函數(shù),用來生成 Set 數(shù)據(jù)結(jié)構(gòu)。接受一個數(shù)組(或類似數(shù)組的對象)作為參數(shù),用來初始化。
const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4]
因?yàn)镾et的成員都是唯一的,所以可用set去除數(shù)組重復(fù)成員。向Set加入值的時(shí)候,不會發(fā)生類型轉(zhuǎn)換,所以5和"5"是兩個不同的值。Set內(nèi)部判斷兩個值是否不同類似于精確相等運(yùn)算符(===),主要的區(qū)別是NaN等于自身,而精確相等運(yùn)算符認(rèn)為NaN不等于自身。Array.from方法可以將 Set 結(jié)構(gòu)轉(zhuǎn)為數(shù)組。
// 去除數(shù)組的重復(fù)成員 [...new Set(array)] let set = new Set(); let a = NaN; let b = NaN; set.add(a); set.add(b); set // Set(1) {NaN} 這表明,在 Set 內(nèi)部,兩個NaN是相等。Set 實(shí)例的屬性和方法 Set 實(shí)例的屬性
size:返回Set實(shí)例的成員總數(shù)。
constructor:指向構(gòu)造函數(shù),默認(rèn)就是Set函數(shù)。
Set 實(shí)例的方法操作方法
add(value):添加某個值,返回Set結(jié)構(gòu)本身。
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value):返回一個布爾值,表示該值是否為Set的成員。
clear():清除所有成員,沒有返回值。
遍歷操作
keys():返回鍵名的遍歷器對象。
values():返回鍵值的遍歷器對象。
entries():返回鍵值對的遍歷器對象。
forEach():使用回調(diào)函數(shù)遍歷每個成員,沒有返回值。第一個參數(shù)是一個處理函數(shù)。該函數(shù)的參數(shù)依次為鍵值、鍵名、集合本身。第二個參數(shù),表示綁定的this對象。
由于 Set 結(jié)構(gòu)沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以keys方法和values方法的行為完全一致。
Set的遍歷順序就是插入順序。通過該特性,當(dāng)用Set保存一個回調(diào)函數(shù)列表,調(diào)用時(shí)就能保證按照添加順序調(diào)用。
Set 結(jié)構(gòu)的實(shí)例默認(rèn)可遍歷,它的默認(rèn)遍歷器生成函數(shù)就是它的values方法。擴(kuò)展運(yùn)算符(...)可用于 Set 結(jié)構(gòu)。
Set.prototype[Symbol.iterator] === Set.prototype.values // trueWeakSet
WeakSet 是一個構(gòu)造函數(shù),可以使用new命令,創(chuàng)建 WeakSet 數(shù)據(jù)結(jié)構(gòu)。可以接受一個數(shù)組或類似數(shù)組的對象作為參數(shù)。(實(shí)際上,任何具有 Iterable 接口的對象,都可以作為 WeakSet 的參數(shù)。)該數(shù)組的所有成員,都會自動成為 WeakSet 實(shí)例對象的成員。
const a = [[1, 2], [3, 4]]; const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
上面的代碼中,a數(shù)組的成員成為 WeakSet 的成員,而不是a數(shù)組本身。
WeakSet 結(jié)構(gòu)有以下三個方法:
WeakSet.prototype.add(value):向 WeakSet 實(shí)例添加一個新成員。
WeakSet.prototype.delete(value):清除 WeakSet 實(shí)例的指定成員。
WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實(shí)例之中。
WeakSet的特點(diǎn)WeakSet 結(jié)構(gòu)與 Set 類似,也是不重復(fù)的值的集合。但是,與Set不同的是:
WeakSet 的成員只能是對象,而不能是其他類型的值。
WeakSet 中的對象都是弱引用,即垃圾回收機(jī)制不考慮 WeakSet 對該對象的引用,也就是說,如果其他對象都不再引用該對象,那么垃圾回收機(jī)制會自動回收該對象所占用的內(nèi)存,不考慮該對象還存在于 WeakSet 之中。
WeakSet 沒有size屬性,不可遍歷。
WeakSet的以上特性決定WeakSet適合臨時(shí)存放一組對象,以及存放跟對象綁定的信息。只要這些對象在外部消失,它在 WeakMap 里面的引用就會自動消失。
MapJavaScript 的對象(Object),本質(zhì)上是鍵值對的集合(Hash 結(jié)構(gòu)),但是傳統(tǒng)上只能用字符串當(dāng)作鍵。
Map 數(shù)據(jù)結(jié)構(gòu)類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當(dāng)作鍵。Map作為構(gòu)造函數(shù),可以接受任何具有 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)作為參數(shù),比如數(shù)組。
//Map構(gòu)造函數(shù)接受數(shù)組作為參數(shù),實(shí)際上執(zhí)行的是下面的算法。 const items = [ ["name", "張三"], ["title", "Author"] ]; const map = new Map(); items.forEach( ([key, value]) => map.set(key, value) );
如果對同一個鍵多次賦值,后面的值將覆蓋前面的值。
如果讀取一個未知的鍵,則返回undefined。
只有對同一個對象的引用,Map 結(jié)構(gòu)才將其視為同一個鍵,因?yàn)镸ap 的鍵實(shí)際上是跟內(nèi)存地址綁定。
const map = new Map(); map.set(["a"], 555); map.get(["a"]) // undefined判斷Map鍵是否相等
Map鍵是對象類型的,內(nèi)存地址相同才相同。
Map鍵是簡單類型(數(shù)字、字符串、布爾值)的,兩個值嚴(yán)格相等視為一個鍵。0和-0是同一個鍵。
Map鍵將NaN和其自身視為同一個鍵。
Map實(shí)例的屬性和方法 Map實(shí)例的屬性size:返回 Map 結(jié)構(gòu)的成員總數(shù)。
Map實(shí)例的方法set(key, value):設(shè)置鍵名key對應(yīng)的鍵值為value,然后返回整個 Map 結(jié)構(gòu)。如果key已經(jīng)有值,則鍵值會被更新,否則就新生成該鍵。
get(key):讀取key對應(yīng)的鍵值,如果找不到key,返回undefined。
has(key):返回一個布爾值,表示某個鍵是否在當(dāng)前 Map 對象之中。
delete(key):刪除某個鍵,返回true。如果刪除失敗,返回false。
clear():清除所有成員,沒有返回值。
遍歷方法
keys():返回鍵名的遍歷器。
values():返回鍵值的遍歷器。
entries():返回所有成員的遍歷器。
forEach():使用回調(diào)函數(shù)遍歷Map的每個成員。第一個參數(shù)是一個處理函數(shù)。該函數(shù)的參數(shù)依次為鍵值、鍵名、集合本身。第二個參數(shù),表示綁定的this對象。
Map 的遍歷順序就是插入順序。
Map 結(jié)構(gòu)的默認(rèn)遍歷器接口(Symbol.iterator屬性),就是entries方法。Map 結(jié)構(gòu)轉(zhuǎn)為數(shù)組結(jié)構(gòu),比較快速的方法是使用擴(kuò)展運(yùn)算符(...)。
WeakMapWeakMap結(jié)構(gòu)與Map結(jié)構(gòu)類似,也是用于生成鍵值對的集合。但是
WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名。
WeakMap的鍵名所指向的對象,不計(jì)入垃圾回收機(jī)制。
WeakMap的鍵名所引用的對象都是弱引用,即垃圾回收機(jī)制不將該引用考慮在內(nèi)。因此,只要所引用的對象的其他引用都被清除,垃圾回收機(jī)制就會釋放該對象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對象和所對應(yīng)的鍵值對會自動消失,不用手動刪除引用。
WeakMap結(jié)構(gòu)有助于防止內(nèi)存泄漏。
WeakMap沒有遍歷操作(即沒有key()、values()和entries()方法),也沒有size屬性,也不支持clear方法。因此,WeakMap只有四個方法可用:get()、set()、has()、delete()。
PromisePromise對象特點(diǎn):
Promise對象有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。
一旦狀態(tài)改變,就不會再變,任何時(shí)候都可以得到這個結(jié)果。Promise對象的狀態(tài)改變,只有兩種可能:從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected。只要這兩種情況發(fā)生,狀態(tài)就不會再變了。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。這與事件(Event)完全不同,事件的特點(diǎn)是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。
狀態(tài)一改變,即調(diào)用Promise 對象的 then方法。
缺點(diǎn):
Promise一旦新建它就會立即執(zhí)行,無法中途取消。
如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部。
當(dāng)處于Pending狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個階段(剛剛開始還是即將完成)。
基本用法Promise對象是一個構(gòu)造函數(shù),用來生成Promise實(shí)例。
var promise = new Promise( function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } });
Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)在Promise構(gòu)造函數(shù)返回新建對象前被調(diào)用,被傳遞resolve和reject函數(shù)。resolve和reject函數(shù)由JavaScript引擎提供,不用自己部署。若參數(shù)函數(shù)拋出一個錯誤,那么該promise 狀態(tài)為rejected。函數(shù)的返回值被忽略。
resolve函數(shù):將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸腜ending變?yōu)镽esolved),將傳給resolve函數(shù)的參數(shù)傳遞出去。
reject函數(shù):將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸腜ending變?yōu)镽ejected),將傳給Promise函數(shù)的參數(shù)傳遞出去。
簡而言之,如果調(diào)用resolve函數(shù)和reject函數(shù)時(shí)帶有參數(shù),那么它們的參數(shù)會被傳遞給回調(diào)函數(shù)。
resolve函數(shù)可以傳遞一個Promise實(shí)例。當(dāng)傳遞的是一個Promise實(shí)例時(shí),其自身狀態(tài)無效,其狀態(tài)由該P(yáng)romise實(shí)例決定。
var p1 = new Promise(function (resolve, reject) { // ... }); var p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上面代碼中p2的resolve方法將p1作為參數(shù),即一個異步操作的結(jié)果是返回另一個異步操作。
注意,這時(shí)p1的狀態(tài)就會傳遞給p2,也就是說,這時(shí)p2自己的狀態(tài)無效了,由p1的狀態(tài)決定p2的狀態(tài)如果p1的狀態(tài)是Pending,那么p2的回調(diào)函數(shù)就會等待p1的狀態(tài)改變;如果p1的狀態(tài)已經(jīng)是Resolved或者Rejected,那么p2的回調(diào)函數(shù)將會立刻執(zhí)行。
Promise.prototype.then()the()方法返回一個新的Promise。因此可以采用鏈?zhǔn)綄懛ā?/p>
promise.then(onFulfilled, onRejected); promise.then(function(value) { // success }, function(error) { // failure });
then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)。第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)镽esolved時(shí)調(diào)用,第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)镽eject時(shí)調(diào)用。這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)。若省略這兩個參數(shù),或者提供非函數(shù),不會產(chǎn)生任何錯誤。
注意:
如果 onFulfilled 或者 onRejected 拋出一個錯誤,或者返回一個拒絕的 Promise ,then 返回一個 rejected Promise。
如果 onFulfilled 或者 onRejected 返回一個 resolves Promise,或者返回任何其他值,或者未返回值,then 返回一個 resolved Promise。
onFulfilled 或者 onRejected是被異步調(diào)用的。異步調(diào)用指的是在本輪“事件循環(huán)”(event loop)的結(jié)束時(shí)執(zhí)行,而不是在下一輪“事件循環(huán)”的開始時(shí)執(zhí)行。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代碼中第一個回調(diào)函數(shù)完成以后,會將返回的json.post作為參數(shù),傳入第二個回調(diào)函數(shù)。若前一個回調(diào)函數(shù)返回的是一個Promise對象,這時(shí)后一個回調(diào)函數(shù),就會等待該P(yáng)romise對象的狀態(tài)發(fā)生變化,才會被調(diào)用。
setTimeout(function(){ console.log("aaa"); }); // using a resolved promise, the "then" block will be triggered instantly, but its handlers will be triggered asynchronously as demonstrated by the console.logs var resolvedProm = Promise.resolve(33); var thenProm = resolvedProm.then(function(value){ console.log("this gets called after the end of the main stack. the value received and returned is: " + value); return value; }); // instantly logging the value of thenProm console.log(thenProm); // using setTimeout we can postpone the execution of a function to the moment the stack is empty setTimeout(function(){ console.log(thenProm); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //this gets called after the end of the main stack. the value received and returned is: 33 //aaa //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
上面代碼中:setTimeout(fn, 0)在下一輪“事件循環(huán)”開始時(shí)執(zhí)行,onFulfilled 在本輪“事件循環(huán)”結(jié)束時(shí)執(zhí)行,console.log(thenProm)則是立即執(zhí)行,因此最先輸出。
若then中無對應(yīng)的回調(diào)函數(shù),則then返回的新promise將會保持原promise的狀態(tài)進(jìn)行調(diào)用。
例如:
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); } timeout(100).then(null, (value) => { console.log("aaa"); }).then((value) => { console.log("ccc"); }, (t) => { console.log("ffffd"); }); //ccc
上面代碼中,timeout函數(shù)中的 Promise狀態(tài)是resolve,但是第一個then中沒有對應(yīng)的回調(diào)函數(shù),因此第一個then返回的是resolve狀態(tài)的Promise。所以第二個then立馬被調(diào)用,輸出"ccc"。
Promise.prototype.catch()Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯誤時(shí)的回調(diào)函數(shù)。該方法返回一個新的Promise。
p.catch(onRejected); p.catch(function(reason) { // 拒絕 });
onRejected 拋出一個錯誤,或者返回一個拒絕的 Promise,則catch返回一個 rejected Promise,否則返回一個resolved Promise。
getJSON("/posts.json").then(function(posts) { // ... }).catch(function(error) { // 處理 getJSON 和 前一個回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯誤 console.log("發(fā)生錯誤!", error); });
上面代碼中,getJSON方法返回一個 Promise 對象,如果該對象狀態(tài)變?yōu)镽esolved,則會調(diào)用then方法指定的回調(diào)函數(shù);如果異步操作拋出錯誤,就會調(diào)用catch方法指定的回調(diào)函數(shù),處理這個錯誤。另外,then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯誤,也會被catch方法捕獲。
一般來說,不要在then方法里面定義Reject狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù)),總是使用catch方法。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
因?yàn)榈诙N寫法可以捕獲前面then方法執(zhí)行中的錯誤,所以建議總是使用catch方法,而不使用then方法的第二個參數(shù)。
重要解析:
var promise = new Promise(function(resolve, reject) { resolve("ok"); throw new Error("test"); }); promise.then(function(value) { console.log(value) }); //ok var promise = new Promise(function(resolve, reject) { resolve("ok"); setTimeout(function() { throw new Error("test") }, 0) }); promise.then(function(value) { console.log(value) }); // ok // Uncaught Error: test
上面代碼中第一個例子中,throw 在resolve語句后面,拋出的錯誤,已經(jīng)被捕獲并處理。但是Promise 的狀態(tài)因?yàn)閞esolve("ok")語句已改變,所以不會再改變。
上面代碼中第二個例子中拋出錯誤時(shí),Promise函數(shù)體已經(jīng)運(yùn)行結(jié)束,所以無法捕捉到該錯誤,就出現(xiàn)了在console中出現(xiàn)"ok"并拋出異常的現(xiàn)象。
詳見Promise源碼中的tryCallTwo和doResolve函數(shù)
Promise.all(iterable):當(dāng)在可迭代參數(shù)中的所有promises被resolve,或者任一 Promise 被 reject時(shí),返回一個新的promise。
iterable:一個可迭代對象,例如 Array。
(1)iterable為空(比如[]),返回一個同步的resolved Promise。
(2)iterable未包含任何的promises(比如[1,2,3]),返回一個異步的resolved Promise。
(3)iterable中的所有promises都是resolve,返回一個異步的resolved Promise。
以上情況中,iterable內(nèi)的所有值將組成一個數(shù)組,傳遞給回調(diào)函數(shù)。
var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "foo"); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] });
(4)只要iterable中的promises有一個被rejected,就立即返回一個異步的rejected Promise。此時(shí)第一個被reject的實(shí)例的返回值,會傳遞給回調(diào)函數(shù)。
Promise.all([1,2,3, Promise.reject(555)]); //Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 555}如何理解返回一個異步的Promise
var p = Promise.all([]); // will be immediately resolved var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously console.log(p); console.log(p2) setTimeout(function(){ console.log("the stack is now empty"); console.log(p2); }); // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: Array(0)} // Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} // the stack is now empty // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: Array(2)}Promise.race()
Promise.race(iterable):方法返回一個新的異步的promise,參數(shù)iterable中只要有一個promise對象"完成(resolve)"或"失敗(reject)",新的promise就會立刻"完成(resolve)"或者"失敗(reject)",并獲得之前那個promise對象的返回值或者錯誤原因。
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)]; var p = Promise.race(resolvedPromisesArray); // immediately logging the value of p console.log(p); // using setTimeout we can execute code after the stack is empty setTimeout(function(){ console.log("the stack is now empty"); console.log(p); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //98 //the stack is now empty //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
若iterable為空,則返回的promise永遠(yuǎn)都是pending狀態(tài)。
若iterable里面包含一個或多個非promise值并且/或者有一個resolved/rejected promise,則新生成的Promise的值為數(shù)組中的能被找到的第一個值。
var foreverPendingPromise = Promise.race([]); var alreadyResolvedProm = Promise.resolve(666); var arr = [foreverPendingPromise, alreadyResolvedProm, "non-Promise value"]; var arr2 = [foreverPendingPromise, "non-Promise value", Promise.resolve(666)]; var p = Promise.race(arr); var p2 = Promise.race(arr2); console.log(p); console.log(p2); setTimeout(function(){ console.log("the stack is now empty"); console.log(p); console.log(p2); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //the stack is now empty //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 666} //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "non-Promise value"}Promise.resolve()
Promise.resolve返回一個Promise對象。
Promise.resolve(value); Promise.resolve(promise); Promise.resolve(thenable);
Promise.resolve方法的參數(shù):
參數(shù)是一個Promise實(shí)例:Promise.resolve將不做任何修改、原封不動地返回這個實(shí)例。
參數(shù)是一個thenable對象:thenable對象指的是具有then方法的對象。Promise.resolve方法將該對象轉(zhuǎn)為Promise對象后,就會立即執(zhí)行thenable對象的then方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 }); //thenable對象的then方法執(zhí)行后,對象p1的狀態(tài)就變?yōu)閞esolved,從而立即執(zhí)行最后那個then方法指定的回調(diào)函數(shù),輸出42。
其他情況:Promise.resolve方法返回一個新的Promise對象,狀態(tài)為Resolved。Promise.resolve方法的參數(shù),會同時(shí)傳給回調(diào)函數(shù)。
var p = Promise.resolve("Hello"); p.then(function (s){ console.log(s) }); // Hello //返回Promise實(shí)例的狀態(tài)從一生成就是Resolved,所以回調(diào)函數(shù)會立即執(zhí)行Promise.reject()
Promise.reject(reason)方法也會返回一個新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected。因此,回調(diào)函數(shù)會立即執(zhí)行。
Promise.reject(reason);
Promise.reject()方法的參數(shù),會原封不動地作為返回的新Promise的[[PromiseValue]]值,變成后續(xù)方法的參數(shù)。
Iterator(遍歷器)JavaScript原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組(Array)和對象(Object),ES6又添加了Map和Set。一個數(shù)據(jù)結(jié)構(gòu)只要部署了Symbol.iterator屬性,就被視為具有iterator接口,就可以用for...of循環(huán)遍歷它的成員。也就是說,for...of循環(huán)內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator方法。任何數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口,就稱這種數(shù)據(jù)結(jié)構(gòu)是”可遍歷的“(iterable)。
Symbol.iterator屬性本身是一個函數(shù),執(zhí)行這個函數(shù),就會返回一個遍歷器。屬性名Symbol.iterator是一個表達(dá)式,返回Symbol對象的iterator屬性,這是一個預(yù)定義好的、類型為Symbol的特殊值,所以要放在方括號內(nèi)。
遍歷器對象的根本特征: 具有next方法。每次調(diào)用next方法,都會返回一個代表當(dāng)前成員的信息對象,該對象具有value和done兩個屬性。
內(nèi)置可迭代對象:String, Array, TypedArray, Map and Set 。
接受可迭代對象作為參數(shù)的:Map([iterable]), WeakMap([iterable]), Set([iterable])、WeakSet([iterable])、Promise.all(iterable), Promise.race(iterable) 以及 Array.from()。
一個對象如果要有可被for...of循環(huán)調(diào)用的Iterator接口,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可)。
對于類似數(shù)組的對象(存在數(shù)值鍵名和length屬性),部署Iterator接口,有一個簡便方法,就是Symbol.iterator方法直接引用數(shù)組的Iterator接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
如果Symbol.iterator方法對應(yīng)的不是遍歷器生成函數(shù)(即會返回一個遍歷器對象),解釋引擎將會報(bào)錯。
調(diào)用Iterator接口的場合解構(gòu)賦值:對數(shù)組和Set結(jié)構(gòu)進(jìn)行解構(gòu)賦值時(shí),會默認(rèn)調(diào)用Symbol.iterator方法。
擴(kuò)展運(yùn)算符:擴(kuò)展運(yùn)算符(...)也會調(diào)用默認(rèn)的iterator接口。因此,可通過(...)方便的將部署了Iterator接口的數(shù)據(jù)接口轉(zhuǎn)為數(shù)組。
let arr = [...iterable];
yield*:yield*后面跟的是一個可遍歷的結(jié)構(gòu),它會調(diào)用該結(jié)構(gòu)的遍歷器接口。
任何接受數(shù)組作為參數(shù)的場合,都調(diào)用了遍歷器接口。
for...of Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([["a",1],["b",2]])) Promise.all() Promise.race()
for...in循環(huán)讀取鍵名。for...of循環(huán)讀取鍵值,但數(shù)組的遍歷器接口只返回具有數(shù)字索引的鍵值。
let arr = [3, 5, 7]; arr.foo = "hello"; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" } //for...of循環(huán)不返回?cái)?shù)組arr的foo屬性
Set 和 Map 結(jié)構(gòu)使用for...of循環(huán)時(shí):
遍歷的順序是按照各個成員被添加進(jìn)數(shù)據(jù)結(jié)構(gòu)的順序。
Set 結(jié)構(gòu)遍歷時(shí),返回的是一個值,而 Map 結(jié)構(gòu)遍歷時(shí),返回的是一個數(shù)組,該數(shù)組的兩個成員分別為當(dāng)前 Map 成員的鍵名和鍵值。
ES6的數(shù)組、Set、Map均有以下方法(返回的都是遍歷器對象,與Object的entries、keys、values方法不同,Object返回的均是數(shù)組。):
entries() 返回一個遍歷器對象,用來遍歷[鍵名, 鍵值]組成的數(shù)組。對于數(shù)組,鍵名就是索引值;對于 Set,鍵名與鍵值相同。Map 結(jié)構(gòu)的 Iterator 接口,默認(rèn)就是調(diào)用entries方法。
keys() 返回一個遍歷器對象,用來遍歷所有的鍵名。
values() 返回一個遍歷器對象,用來遍歷所有的鍵值。
for...of循環(huán)能正確識別字符串中的32位 UTF-16 字符。
可通過Array.from方法將類似數(shù)組的對象轉(zhuǎn)為數(shù)組。
forEach:無法中途跳出forEach循環(huán),break命令或return命令都不能奏效。
for...in:不僅遍歷數(shù)字鍵名,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵。
for...of循環(huán)可以與break、continue和return配合使用,提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口。
GeneratorGenerator 函數(shù)是一個普通函數(shù),有以下特征:
function關(guān)鍵字與函數(shù)名之間有一個星號。
函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)。
調(diào)用Generator 函數(shù),就是在函數(shù)名后面加上一對圓括號。不過,調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,而是返回一個遍歷器對象。調(diào)用遍歷器對象的next方法,就會返回一個有著value和done兩個屬性的對象。value屬性就是yield表達(dá)式或return后面那個表達(dá)式的值;done屬性是一個布爾值,表示是否遍歷結(jié)束。每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個yield表達(dá)式(或return語句)為止。Generator 函數(shù)不能當(dāng)構(gòu)造器使用。
function* f() {} var obj = new f; // throws "TypeError: f is not a constructor"yield 表達(dá)式
遍歷器對象的next方法的運(yùn)行邏輯:
遇到y(tǒng)ield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個表達(dá)式的值,作為返回的對象的value屬性值。
下一次調(diào)用next方法時(shí),再繼續(xù)往下執(zhí)行,直到遇到下一個yield表達(dá)式。
如果沒有再遇到新的yield表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束,直到return語句為止,并將return語句后面的表達(dá)式的值,作為返回的對象的value屬性值。
如果該函數(shù)沒有return語句,則返回的對象的value屬性值為undefined。
function* demo() { console.log("Hello" + (yield)); console.log("Hello" + (yield 123)); } var a=demo(); a.next(); //Object {value: undefined, done: false} 第一次運(yùn)行了yield之后就停止了。 a.next(); //Helloundefined //Object {value: 123, done: false} 第二次將之前的hello打印,并運(yùn)行yield 123之后停止。 a.next(); //Helloundefined //Object {value: undefined, done: true}
yield表達(dá)式與return語句:
相似之處:能返回緊跟在語句后面的那個表達(dá)式的值。
不同之處:每次遇到y(tǒng)ield,函數(shù)暫停執(zhí)行,下一次再從該位置后繼續(xù)向后執(zhí)行,即使運(yùn)行到最后一個yield ,其返回對象的done仍為false。return語句執(zhí)行后即代表該遍歷結(jié)束,返回對象的done為true。
function* helloWorldGenerator() { yield "hello"; return "ending"; yield "world"; } var hw = helloWorldGenerator(); hw.next(); // Object {value: "hello", done: false} hw.next(); // Object {value: "ending", done: true} hw.next(); // Object {value: undefined, done: true}
Generator 函數(shù)可以不用yield表達(dá)式,這時(shí)就變成了一個單純的暫緩執(zhí)行函數(shù)。但yield表達(dá)式只能用在 Generator 函數(shù)里面,用在其他地方都會報(bào)錯。
function* f() { console.log("執(zhí)行了!") } var generator = f(); setTimeout(function () { generator.next() }, 2000);
上面代碼中函數(shù)f如果是普通函數(shù),在為變量generator賦值時(shí)就會執(zhí)行。但是,函數(shù)f是一個 Generator 函數(shù),就變成只有調(diào)用next方法時(shí),函數(shù)f才會執(zhí)行。
yield表達(dá)式如果用在另一個表達(dá)式之中,必須放在圓括號里面。如果用作函數(shù)參數(shù)或放在賦值表達(dá)式的右邊,可以不加括號。
與 Iterator 接口的關(guān)系任意一個對象的Symbol.iterator方法,等于該對象的遍歷器生成函數(shù),調(diào)用該函數(shù)會返回該對象的一個遍歷器對象。由于 Generator 函數(shù)就是遍歷器生成函數(shù),因此可以把 Generator 賦值給對象的Symbol.iterator屬性。
Generator 函數(shù)執(zhí)行后,返回一個遍歷器對象。該對象本身也具有Symbol.iterator屬性,執(zhí)行后返回自身。
function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // truenext 方法的參數(shù)
yield表達(dá)式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個參數(shù),該參數(shù)就會被當(dāng)作上一個yield表達(dá)式的返回值。注意,由于next方法的參數(shù)表示上一個yield表達(dá)式的返回值,所以第一次使用next方法時(shí),不用帶參數(shù)。
for...of 循環(huán)for...of循環(huán)可以自動遍歷 Generator 函數(shù)時(shí)生成的Iterator對象。
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
注意:一旦next方法的返回對象的done屬性為true,for...of循環(huán)就會中止,且不包含該返回對象,所以上面代碼的return語句返回的6,不包括在for...of循環(huán)之中。
for...of的本質(zhì)是一個while循環(huán),通俗的講,就是運(yùn)行對象的Symbol.iterator方法(即遍歷器生成函數(shù)),得到遍歷器對象,再不停的調(diào)用遍歷器對象的next方法運(yùn)行,直到遍歷結(jié)束。類似如下:
var it = foo(); var res = it.next(); while (!res.done){ // ... res = it.next(); }Generator.prototype.throw()
throw() 方法:向Generator函數(shù)內(nèi)部拋出異常,并恢復(fù)生成器的執(zhí)行,返回帶有 done 及 value 兩個屬性的對象。
gen.throw(exception)
exception:要拋出的異常。
該方法可以在Generator 函數(shù)體外拋出錯誤,然后在 Generator 函數(shù)體內(nèi)捕獲。
var g = function* () { try { yield; } catch (e) { console.log("內(nèi)部捕獲", e); } }; var i = g(); i.next(); try { i.throw("a"); i.throw("b"); } catch (e) { console.log("外部捕獲", e); } // 內(nèi)部捕獲 a // 外部捕獲 b
上面代碼中,遍歷器對象i連續(xù)拋出兩個錯誤。第一個錯誤被 Generator 函數(shù)體內(nèi)的catch語句捕獲。i第二次拋出錯誤,由于 Generator 函數(shù)內(nèi)部的catch語句已經(jīng)執(zhí)行過了,不會再捕捉到這個錯誤了,所以這個錯誤就被拋出了 Generator 函數(shù)體,被函數(shù)體外的catch語句捕獲。
throw方法可以接受一個參數(shù),該參數(shù)會被catch語句接收,建議拋出Error對象的實(shí)例。遍歷器對象的throw方法和全局的throw命令不一樣。全局的throw命令只能被該命令外的catch語句捕獲,且不會再繼續(xù)try代碼塊里面剩余的語句了。
如果 Generator 函數(shù)內(nèi)部沒有部署try...catch代碼塊,那么throw方法拋出的錯誤,將被外部try...catch代碼塊捕獲。如果 Generator 函數(shù)內(nèi)部和外部,都沒有部署try...catch代碼塊,那么程序?qū)?bào)錯,直接中斷執(zhí)行。
var g = function* () { while (true) { yield; console.log("內(nèi)部捕獲", e); } }; var i = g(); i.next(); try { i.throw("a"); i.throw("b"); } catch (e) { console.log("外部捕獲", e); } // 外部捕獲 a
throw方法被捕獲以后,會附帶執(zhí)行下一條yield表達(dá)式。也就是說,會附帶執(zhí)行一次next方法。
var gen = function* gen(){ try { yield console.log("a"); console.log("b"); } catch (e) { console.log("錯誤被捕獲"); } yield console.log("c"); yield console.log("d"); } var g = gen(); g.next(); //a //Object {value: undefined, done: false} g.throw(); //錯誤被捕獲 //c //Object {value: undefined, done: false} g.next(); //d //Object {value: undefined, done: false}
上面的代碼可以看出,g.throw方法是先拋出異常,再自動執(zhí)行一次next方法,因此可以看到?jīng)]有打印b,但是打印了c。
Generator 函數(shù)體外拋出的錯誤,可以在函數(shù)體內(nèi)捕獲;反過來,Generator 函數(shù)體內(nèi)拋出的錯誤,也可以被函數(shù)體外的catch捕獲。
一旦 Generator 執(zhí)行過程中拋出錯誤,且沒有被內(nèi)部捕獲,就不會再執(zhí)行下去了。如果此后還調(diào)用next方法,將返回一個value屬性等于undefined、done屬性等于true的對象,即 JavaScript 引擎認(rèn)為這個 Generator 已經(jīng)運(yùn)行結(jié)束了。
Generator.prototype.return()Generator.prototype.return可以返回給定的值,并且終結(jié)遍歷Generator函數(shù)。若該方法被調(diào)用時(shí),Generator函數(shù)已結(jié)束,則Generator函數(shù)將保持結(jié)束的狀態(tài),但是提供的參數(shù)將被設(shè)置為返回對象的value屬性的值。
遍歷器對象調(diào)用return方法后,返回值的value屬性就是return方法的參數(shù)foo。并且,Generator函數(shù)的遍歷就終止了,返回值的done屬性為true,以后再調(diào)用next方法,done屬性總是返回true。如果return方法調(diào)用時(shí),不提供參數(shù),則返回值的value屬性為undefined。
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return("foo") // { value: "foo", done: true } g.next() // { value: undefined, done: true } g.return() // {value: undefined, done: true} g.return("111") //{value: "111", done: true}
上面代碼中,g.return("111")調(diào)用時(shí), Generator函數(shù)的遍歷已經(jīng)終止,所以返回的對象的done值仍為true,但是value值會被設(shè)置為"111"。
如果 Generator 函數(shù)內(nèi)部有try...finally代碼塊,那么當(dāng)return方法執(zhí)行時(shí)的語句在 Generator 函數(shù)內(nèi)部的try代碼塊中時(shí),return方法會推遲到finally代碼塊執(zhí)行完再執(zhí)行。
function* numbers () { yield 1; try { yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next(); //Object {value: 1, done: false} g.return(7); //Object {value: 7, done: true} //return執(zhí)行時(shí)還未在try語句塊內(nèi),所以返回{value: 7, done: true}并終止遍歷。 function* numbers () { yield 1; try { yield 2; yield 3; yield 33; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next(); // Object {value: 1, done: false} g.next(); // Object {value: 2, done: false} g.return(7); // Object {value: 4, done: false} g.next(); // Object {value: 5, done: false} g.next(); // Object {value: 7, done: true} //return執(zhí)行時(shí)已在try語句塊內(nèi),運(yùn)行時(shí)直接跳至finally語句塊執(zhí)行,并在該語句塊內(nèi)的代碼執(zhí)行完后,所以返回{value: 7, done: true}并終止遍歷。yield* 表達(dá)式
yield* expression :expression 可以是一個generator 或可迭代對象。yield * 表達(dá)式自身的值是當(dāng)?shù)麝P(guān)閉時(shí)返回的值(即,當(dāng)done時(shí)為true)。
function* foo() { yield "a"; yield "b"; } function* bar() { yield "x"; yield* foo(); yield "y"; } // 等同于 function* bar() { yield "x"; yield "a"; yield "b"; yield "y"; } // 等同于 function* bar() { yield "x"; for (let v of foo()) { yield v; } yield "y"; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
yield*后面的 Generator 函數(shù)(沒有return語句時(shí)),等同于在 Generator 函數(shù)內(nèi)部,部署一個for...of循環(huán)。有return語句時(shí),若想遍歷出return返回的值,則需要用var value = yield* iterator的形式獲取return語句的值。
function* foo() { yield "a"; yield "b"; return "mm"; } function* bar() { yield "x"; yield* foo(); yield "y"; } for(var t of bar()){ console.log(t); } //x //a //b //y //yield* foo()寫法無法遍歷出foo里面的“mm”。 function* foo() { yield "a"; yield "b"; return "mm"; } function* bar() { yield "x"; yield yield* foo(); yield "y"; } for(var t of bar()){ console.log(t); } //x //a //b //mm //y //yield* foo()運(yùn)行返回的值就是“mm”,所以yield yield* foo()可以遍歷出“mm”。
任何數(shù)據(jù)結(jié)構(gòu)只要有 Iterator 接口,就可以被yield*遍歷。yield*就相當(dāng)于是使用for...of進(jìn)行了循環(huán)。
作為對象屬性的Generator函數(shù)如果一個對象的屬性是 Generator 函數(shù),則需在屬性前面加一個星號。
let obj = { * myGeneratorMethod() { ··· } }; //等同于 let obj = { myGeneratorMethod: function* () { // ··· } };Generator 函數(shù)的this
Generator 函數(shù)總是返回一個遍歷器,ES6 規(guī)定這個遍歷器是 Generator 函數(shù)的實(shí)例,也繼承了 Generator 函數(shù)的prototype對象上的方法。Generator函數(shù)不能跟new命令一起用,會報(bào)錯。
function* g() {} g.prototype.hello = function () { return "hi!"; }; let obj = g(); obj.hello() // "hi!"
上面代碼可以看出,obj對象是Generator 函數(shù)g的實(shí)例。但是,如果把g當(dāng)作普通的構(gòu)造函數(shù),并不會生效,因?yàn)間返回的總是遍歷器對象,而不是this對象。
function* g() { this.a = 11; } let obj = g(); obj.a // undefined
上面代碼中,Generator函數(shù)g在this對象上面添加了一個屬性a,但是obj對象拿不到這個屬性。
應(yīng)用場景
用來處理異步操作,改寫回調(diào)函數(shù)。即把異步操作寫在yield表達(dá)式里面,異步操作的后續(xù)操作放在yield表達(dá)式下面。
function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next(); //上面為通過 Generator 函數(shù)部署 Ajax 操作。
控制流管理
利用 Generator 函數(shù),在任意對象上部署 Iterator 接口。
作為數(shù)據(jù)結(jié)構(gòu)。
Generator 函數(shù)的異步應(yīng)用Generator 函數(shù)可以暫停執(zhí)行和恢復(fù)執(zhí)行,這是它能封裝異步任務(wù)的根本原因。整個 Generator 函數(shù)就是一個封裝的異步任務(wù)。異步操作需要暫停的地方,都用yield語句注明。
Generator 函數(shù)可以進(jìn)行數(shù)據(jù)交換。next返回值的value屬性,是 Generator 函數(shù)向外輸出數(shù)據(jù);next方法還可以接受參數(shù),向 Generator 函數(shù)體內(nèi)輸入數(shù)據(jù)。
Generator 函數(shù)可以部署錯誤處理代碼,捕獲函數(shù)體外拋出的錯誤。
function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw("出錯了"); // 出錯了
上面代碼的最后一行,Generator 函數(shù)體外,使用指針對象的throw方法拋出的錯誤,可以被函數(shù)體內(nèi)的try...catch代碼塊捕獲。這意味著,出錯的代碼與處理錯誤的代碼,實(shí)現(xiàn)了時(shí)間和空間上的分離,這對于異步編程無疑是很重要的。
async 函數(shù)async函數(shù)返回一個 Promise 對象,可以使用then方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await就會先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。
function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add1(x) { var a = resolveAfter2Seconds(20); var b = resolveAfter2Seconds(30); return x + await a + await b; } add1(10).then(v => { console.log(v); // prints 60 after 2 seconds. }); async function add2(x) { var a = await resolveAfter2Seconds(20); var b = await resolveAfter2Seconds(30); return x + a + b; } add2(10).then(v => { console.log(v); // prints 60 after 4 seconds. });
async 函數(shù)有多種使用形式。
// 函數(shù)聲明 async function foo() {} // 函數(shù)表達(dá)式 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(…); // 箭頭函數(shù) const foo = async () => {};
調(diào)用async函數(shù)時(shí)會返回一個 promise 對象。當(dāng)這個async函數(shù)返回一個值時(shí),promise 的 resolve 方法將會處理這個返回值;當(dāng)異步函數(shù)拋出的是異常或者非法值時(shí),promise 的 reject 方法將處理這個異常值。
async function f() { throw new Error("出錯了"); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出錯了
async函數(shù)返回的 Promise 對象,必須等到內(nèi)部所有await命令后面的 Promise 對象執(zhí)行完,才會發(fā)生狀態(tài)改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數(shù)內(nèi)部的異步操作執(zhí)行完,才會執(zhí)行then方法指定的回調(diào)函數(shù)。
await 命令await expression:會造成異步函數(shù)停止執(zhí)行并且等待 promise 的解決后再恢復(fù)執(zhí)行。若expression是Promise 對象,則返回expression的[[PromiseValue]]值,若expression不是Promise 對象,則直接返回該expression。
async function f2() { var y = await 20; console.log(y); // 20 } f2();
await命令后面一般是一個 Promise 對象。如果不是,會被轉(zhuǎn)成一個立即resolve的 Promise 對象。await命令后面的 Promise 對象如果變?yōu)閞eject狀態(tài),則會throws異常值,因此reject的參數(shù)會被catch方法的回調(diào)函數(shù)接收到。只要一個await語句后面的 Promise 變?yōu)閞eject,那么整個async函數(shù)都會中斷執(zhí)行。
async function f() { await Promise.reject("出錯了"); await Promise.resolve("hello world"); // 第二個await語句是不會執(zhí)行的 }錯誤處理
如果await后面的異步操作出錯,那么等同于async函數(shù)返回的 Promise 對象被reject。防止出錯的方法,是將其放在try...catch代碼塊之中。
async function f() { await new Promise(function (resolve, reject) { throw new Error("出錯了"); }); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // Error:出錯了
上面代碼中,async函數(shù)f執(zhí)行后,await后面的 Promise 對象會拋出一個錯誤對象,導(dǎo)致catch方法的回調(diào)函數(shù)被調(diào)用,它的參數(shù)就是拋出的錯誤對象。
使用注意點(diǎn)
最好把a(bǔ)wait命令放在try...catch代碼塊中。因?yàn)閍wait命令后面的Promise對象,運(yùn)行結(jié)果可能是rejected。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一種寫法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }; }
多個await命令后面的異步操作,如果不存在繼發(fā)關(guān)系,最好讓它們同時(shí)觸發(fā)。同時(shí)觸發(fā)可以使用Promise.all。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); }
await命令只能用在async函數(shù)之中,如果用在普通函數(shù),就會報(bào)錯。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 報(bào)錯。 因?yàn)閍wait用在普通函數(shù)之中 docs.forEach(function (doc) { await db.post(doc); }); }async 函數(shù)的實(shí)現(xiàn)原理
async 函數(shù)的實(shí)現(xiàn)原理,就是將 Generator 函數(shù)和自動執(zhí)行器,包裝在一個函數(shù)里。
異步遍歷的接口異步遍歷器的最大的語法特點(diǎn),就是調(diào)用遍歷器的next方法,返回的是一個 Promise 對象。
asyncIterator .next() .then( ({ value, done }) => /* ... */ );
上面代碼中,asyncIterator是一個異步遍歷器,調(diào)用next方法以后,返回一個 Promise 對象。因此,可以使用then方法指定,這個 Promise 對象的狀態(tài)變?yōu)閞esolve以后的回調(diào)函數(shù)。回調(diào)函數(shù)的參數(shù),則是一個具有value和done兩個屬性的對象,這個跟同步遍歷器是一樣的。
一個對象的同步遍歷器的接口,部署在Symbol.iterator屬性上面。同樣地,對象的異步遍歷器接口,部署在Symbol.asyncIterator屬性上面。不管是什么樣的對象,只要它的Symbol.asyncIterator屬性有值,就表示應(yīng)該對它進(jìn)行異步遍歷。
for await...offor...of循環(huán)用于遍歷同步的 Iterator 接口。新引入的for await...of循環(huán),則是用于遍歷異步的 Iterator 接口。for await...of循環(huán)也可以用于同步遍歷器。
async function f() { for await (const x of createAsyncIterable(["a", "b"])) { console.log(x); } } // a // b
上面代碼中,createAsyncIterable()返回一個異步遍歷器,for...of循環(huán)自動調(diào)用這個遍歷器的next方法,會得到一個Promise對象。await用來處理這個Promise對象,一旦resolve,就把得到的值(x)傳入for...of的循環(huán)體。
異步Generator函數(shù)在語法上,異步 Generator 函數(shù)就是async函數(shù)與 Generator 函數(shù)的結(jié)合。
async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } }
上面代碼中,異步操作前面使用await關(guān)鍵字標(biāo)明,即await后面的操作,應(yīng)該返回Promise對象。凡是使用yield關(guān)鍵字的地方,就是next方法的停下來的地方,它后面的表達(dá)式的值(即await file.readLine()的值),會作為next()返回對象的value屬性。
Classconstructor定義構(gòu)造方法,this關(guān)鍵字代表實(shí)例對象。定義“類”的方法的時(shí)候,前面不需要加上function這個關(guān)鍵字,直接把函數(shù)定義放進(jìn)去了就可以了。另外,方法之間不需要逗號分隔,加了會報(bào)錯。類的數(shù)據(jù)類型就是函數(shù),類的原型的constructor指向類自身。使用的時(shí)候,對類使用new命令。
//定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return "(" + this.x + ", " + this.y + ")"; } static distance() { } } typeof Point // "function" Point === Point.prototype.constructor // true
類的一般的方法都定義在類的prototype屬性上面。在類的實(shí)例上面調(diào)用方法,其實(shí)就是調(diào)用原型上的方法。類的內(nèi)部所有定義的方法,都是不可枚舉的。類的靜態(tài)方法只能用類來調(diào)用,不能用類的實(shí)例調(diào)用。如果在實(shí)例上調(diào)用靜態(tài)方法,會拋出一個錯誤,表示不存在該方法。父類的靜態(tài)方法,可以被子類繼承。
class Point { constructor(){ // ... } toString(){ // ... } toValue(){ // ... } } // 等同于 Point.prototype = { toString(){}, toValue(){} };
類的屬性名,可以采用表達(dá)式。
let methodName = "getArea"; class Square{ constructor(length) { // ... } [methodName]() { // ... } } //Square類的方法名getArea,是從表達(dá)式得到的。constructor方法
constructor方法是類的默認(rèn)方法,通過new命令生成對象實(shí)例時(shí),自動調(diào)用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認(rèn)添加。
constructor方法默認(rèn)返回實(shí)例對象(即this),也可以指定返回另外一個對象。類的構(gòu)造函數(shù),不使用new是沒法調(diào)用的,會報(bào)錯。這是它跟普通構(gòu)造函數(shù)的一個主要區(qū)別,后者不用new也可以執(zhí)行。
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
上面代碼中,constructor函數(shù)返回一個全新的對象,結(jié)果導(dǎo)致實(shí)例對象不是Foo類的實(shí)例。
類的實(shí)例對象生成類的實(shí)例對象的寫法,也是使用new命令。如果忘記加上new,像函數(shù)那樣調(diào)用Class,將會報(bào)錯。類里面定義的屬性除了定義在this上的,其他都是定義在原型上的。定義在this上的屬性各實(shí)例對象各自有一份。類的所有實(shí)例共享一個原型對象。
Class不存在變量提升(hoist),因此先使用,后定義會報(bào)錯。
new Foo(); // ReferenceError class Foo {}Class表達(dá)式
類也可以使用表達(dá)式的形式定義。
const MyClass = class Me { getClassName() { return Me.name; } };
上面代碼使用表達(dá)式定義了一個類。需要注意的是,這個類的名字是MyClass而不是Me,Me只在Class的內(nèi)部代碼可用,指代當(dāng)前類。
如果類的內(nèi)部沒用到的話,可以省略Me,也就是可以寫成下面的形式。
const MyClass = class { /* ... */ };
采用Class表達(dá)式,可以寫出立即執(zhí)行的Class。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }("張三"); person.sayName(); // "張三" // person是一個立即執(zhí)行的類的實(shí)例。
ES6不提供私有方法。
this的指向類的方法內(nèi)部如果含有this,它默認(rèn)指向類的實(shí)例。但是,必須非常小心,一旦多帶帶使用該方法,很可能報(bào)錯。注意,如果靜態(tài)方法包含this關(guān)鍵字,這個this指的是類,而不是實(shí)例。
class Logger { printName(name = "there") { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property "print" of undefined
上面代碼中,printName方法中的this,默認(rèn)指向Logger類的實(shí)例。但是,如果將這個方法提取出來多帶帶使用,this會指向該方法運(yùn)行時(shí)所在的環(huán)境,因?yàn)檎也坏絧rint方法而導(dǎo)致報(bào)錯。
一個比較簡單的解決方法是,在構(gòu)造方法中綁定this。
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... }
另一種解決方法是使用箭頭函數(shù)。
還有一種解決方法是使用Proxy,獲取方法的時(shí)候,自動綁定this。
function selfish (target) { const cache = new WeakMap(); const handler = { get (target, key) { const value = Reflect.get(target, key); if (typeof value !== "function") { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy; } const logger = selfish(new Logger());嚴(yán)格模式
類和模塊的內(nèi)部,默認(rèn)就是嚴(yán)格模式,所以不需要使用use strict指定運(yùn)行模式。只要你的代碼寫在類或模塊之中,就只有嚴(yán)格模式可用。
name屬性本質(zhì)上,ES6的類只是ES5的構(gòu)造函數(shù)的一層包裝,所以函數(shù)的許多特性都被Class繼承,包括name屬性。name屬性總是返回緊跟在class關(guān)鍵字后面的類名。
class Point {} Point.name // "Point"Class的繼承
Class之間可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 調(diào)用父類的constructor(x, y) this.color = color; } toString() { return this.color + " " + super.toString(); // 調(diào)用父類的toString() } }
子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會報(bào)錯。這是因?yàn)樽宇悰]有自己的this對象,而是繼承父類的this對象,然后對其進(jìn)行加工。如果不調(diào)用super方法,子類就得不到this對象。
ES6的繼承實(shí)質(zhì)是先創(chuàng)造父類的實(shí)例對象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this。
在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后,才可以使用this關(guān)鍵字,否則會報(bào)錯。這是因?yàn)樽宇悓?shí)例的構(gòu)建,是基于對父類實(shí)例加工,只有super方法才能返回父類實(shí)例。
如果子類沒有定義constructor方法,以下方法會被默認(rèn)添加。因此,不管有沒有顯式定義,任何一個子類都有constructor方法。
constructor(...args) { super(...args); }類的prototype屬性和__proto__屬性
Class同時(shí)有prototype屬性和__proto__屬性,因此同時(shí)存在兩條繼承鏈。
子類的__proto__屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。
子類prototype屬性的__proto__屬性,總是指向父類的prototype屬性。
類的繼承是按照下面的模式實(shí)現(xiàn)的。
class A { } class B { } Object.setPrototypeOf(B.prototype, A.prototype); Object.setPrototypeOf(B, A); const b = new B();
而Object.setPrototypeOf方法的實(shí)現(xiàn)如下:
Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
因此,就得到如下結(jié)果。
Object.setPrototypeOf(B.prototype, A.prototype); // 等同于 B.prototype.__proto__ = A.prototype; Object.setPrototypeOf(B, A); // 等同于 B.__proto__ = A;Extends 的繼承目標(biāo)
extends關(guān)鍵字后面可以跟多種類型的值。
class B extends A { }
上面代碼的A,只要是一個有prototype屬性的函數(shù),就能被B繼承。由于函數(shù)都有prototype屬性(除了Function.prototype函數(shù)),因此A可以是任意函數(shù)。
class A { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true
上面代碼中,A作為一個基類(即不存在任何繼承),就是一個普通函數(shù),所以直接繼承Function.prototype。A.prototype是一個對象,所以A.prototype.__proto__指向構(gòu)造函數(shù)(Object)的prototype屬性。
class A extends null { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === undefined // true //等同于 class C extends null { constructor() { return Object.create(null); } }
上面代碼中,子類繼承null。
Object.getPrototypeOf()Object.getPrototypeOf方法可以用來從子類上獲取父類。因此,可以使用這個方法判斷,一個類是否繼承了另一個類。
Object.getPrototypeOf(ColorPoint) === Point //truesuper 關(guān)鍵字
super這個關(guān)鍵字,既可以當(dāng)作函數(shù)使用,也可以當(dāng)作對象使用。
(一) super作為函數(shù)調(diào)用時(shí),代表父類的構(gòu)造函數(shù),且super()只能用在子類的構(gòu)造函數(shù)之中,用在其他地方就會報(bào)錯。ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)。
class A {} class B extends A { constructor() { super(); } }
子類B的構(gòu)造函數(shù)之中的super(),代表調(diào)用父類的構(gòu)造函數(shù)。super()在這里相當(dāng)于A.prototype.constructor.call(this)。
(二) super作為對象時(shí),在普通方法中,指向父類的原型對象(當(dāng)指向父類的原型對象時(shí),定義在父類實(shí)例上的方法或?qū)傩裕菬o法通過super調(diào)用的。);在靜態(tài)方法中,指向父類。
class A { p() { return 2; } static m() { console.log("父類的m方法被調(diào)用") } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } static show() { super.m(); } } let b = new B(); B.show(); //父類的m方法被調(diào)用
上面代碼中,子類B的constructor中的super.p()在普通方法中,指向A.prototype,所以super.p()就相當(dāng)于A.prototype.p()。子類B的show方法中的super.m()在靜態(tài)方法中,所以super.m()就相當(dāng)于A.m()。
ES6 規(guī)定,通過super調(diào)用父類的方法時(shí),super會綁定子類的this。
class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } m() { super.print(); } } let b = new B(); b.m() // 2
上面代碼中,super.print()雖然調(diào)用的是A.prototype.print(),但是A.prototype.print()會綁定子類B的this,導(dǎo)致輸出的是2。也就是說,實(shí)際上執(zhí)行的是super.print.call(this)。
通過super對某個屬性賦值,這時(shí)super就是this,賦值的屬性會變成子類實(shí)例的屬性。
class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined console.log(this.x); // 3 } } let b = new B();
上面代碼中,super.x賦值為3,這時(shí)等同于對this.x賦值為3。而當(dāng)讀取super.x的時(shí)候,讀的是A.prototype.x,所以返回undefined。
注意,使用super的時(shí)候,必須顯式指定是作為函數(shù)、還是作為對象使用,否則會報(bào)錯。
class A {} class B extends A { constructor() { super(); console.log(super); // 報(bào)錯 } } //console.log(super)當(dāng)中的super,無法看出是作為函數(shù)使用,還是作為對象使用,所以 JavaScript 引擎解析代碼的時(shí)候就會報(bào)錯。實(shí)例的__proto__屬性
子類實(shí)例的__proto__屬性的__proto__屬性,指向父類實(shí)例的__proto__屬性。
class Point{ } class ColorPoint extends Point{ constructor(){ super(); } } var p1 = new Point(); var p2 = new ColorPoint(); p2.__proto__.__proto__ === p1.__proto__ // true原生構(gòu)造函數(shù)的繼承
原生構(gòu)造函數(shù)是指語言內(nèi)置的構(gòu)造函數(shù),通常用來生成數(shù)據(jù)結(jié)構(gòu)。ECMAScript的原生構(gòu)造函數(shù)大致有下面這些。
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
extends關(guān)鍵字不僅可以用來繼承類,還可以用來繼承原生的構(gòu)造函數(shù)。
注意,繼承Object的子類,有一個行為差異。
class NewObj extends Object{ constructor(){ super(...arguments); } } var o = new NewObj({attr: true}); console.log(o.attr === true); // false
上面代碼中,NewObj繼承了Object,但是無法通過super方法向父類Object傳參。這是因?yàn)镋S6改變了Object構(gòu)造函數(shù)的行為,一旦發(fā)現(xiàn)Object方法不是通過new Object()這種形式調(diào)用,ES6規(guī)定Object構(gòu)造函數(shù)會忽略參數(shù)。
Class的取值函數(shù)(getter)和存值函數(shù)(setter)在Class內(nèi)部可以使用get和set關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。存值函數(shù)和取值函數(shù)是設(shè)置在屬性的descriptor對象上的。
class MyClass { constructor() { // ... } get prop() { return "getter"; } set prop(value) { console.log("setter: "+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // "getter" //代碼中,prop屬性有對應(yīng)的存值函數(shù)和取值函數(shù),因此賦值和讀取行為都被自定義了。Class的靜態(tài)方法
在一個方法前,加上static關(guān)鍵字,則是靜態(tài)方法。靜態(tài)方法不會被實(shí)例繼承,而是直接通過類來調(diào)用。因此在實(shí)例上調(diào)用靜態(tài)方法,會拋出一個錯誤,表示不存在該方法。
class Foo { static classMethod() { return "hello"; } } Foo.classMethod() // "hello" var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
注意,如果靜態(tài)方法包含this關(guān)鍵字,這個this指的是類,而不是實(shí)例。靜態(tài)方法可以與非靜態(tài)方法重名。父類的靜態(tài)方法,可以被子類繼承。
class Foo { static bar () { this.baz(); } static baz () { console.log("hello"); } baz () { console.log("world"); } } Foo.bar() // hello
上面代碼中,靜態(tài)方法bar調(diào)用了this.baz,這里的this指的是Foo類,而不是Foo的實(shí)例,等同于調(diào)用Foo.baz。
Class的靜態(tài)屬性和實(shí)例屬性靜態(tài)屬性指的是Class本身的屬性,即Class.propname,而不是定義在實(shí)例對象(this)上的屬性。因?yàn)镋S6明確規(guī)定,Class內(nèi)部只有靜態(tài)方法,沒有靜態(tài)屬性。所以目前只有下面這種寫法。
//為Foo類定義了一個靜態(tài)屬性prop class Foo { } Foo.prop = 1; Foo.prop // 1
ES7有一個靜態(tài)屬性的提案,目前Babel轉(zhuǎn)碼器支持。這個提案規(guī)定:
類的實(shí)例屬性可以用等式,寫入類的定義之中。
class MyClass { myProp = 42; constructor() { console.log(this.myProp); // 42 } }
類的靜態(tài)屬性只要在上面的實(shí)例屬性寫法前面,加上static關(guān)鍵字就可以了。
// 老寫法 class Foo { // ... } Foo.prop = 1; // 新寫法 class Foo { static prop = 1; }類的私有屬性
目前,有一個提案,為class加了私有屬性。方法是在屬性名之前,使用#表示。#也可以用來寫私有方法。私有屬性可以指定初始值,在構(gòu)造函數(shù)執(zhí)行時(shí)進(jìn)行初始化。
class Point { #x; constructor(x = 0) { #x = +x; } get x() { return #x } set x(value) { #x = +value } #sum() { return #x; } }
上面代碼中,#x就表示私有屬性x,在Point類之外是讀取不到這個屬性的。還可以看到,私有屬性與實(shí)例的屬性是可以同名的(比如,#x與get x())。
new.target屬性ES6為new命令引入了一個new.target屬性,(在構(gòu)造函數(shù)中)返回new命令作用于的那個構(gòu)造函數(shù)。如果構(gòu)造函數(shù)不是通過new命令調(diào)用的,new.target會返回undefined,因此這個屬性可以用來確定構(gòu)造函數(shù)是怎么調(diào)用的。
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Error("必須使用new生成實(shí)例"); } } // 另一種寫法 function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error("必須使用new生成實(shí)例"); } } var person = new Person("張三"); // 正確 var notAPerson = Person.call(person, "張三"); // 報(bào)錯 //上面代碼確保構(gòu)造函數(shù)只能通過new命令調(diào)用。
Class內(nèi)部調(diào)用new.target,返回當(dāng)前Class。子類繼承父類時(shí),new.target會返回子類。在函數(shù)外部使用new.target會報(bào)錯。
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); // ... } } class Square extends Rectangle { constructor(length) { super(length, length); } } var obj = new Square(3
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/83292.html
摘要:以往的異步方法無外乎回調(diào)函數(shù)和。出錯了出錯了總結(jié)接口遍歷器對象除了具有方法,還可以具有方法和方法。函數(shù)調(diào)用函數(shù),返回一個遍歷器對象,代表函數(shù)的內(nèi)部指針。 引言 接觸過Ajax請求的會遇到過異步調(diào)用的問題,為了保證調(diào)用順序的正確性,一般我們會在回調(diào)函數(shù)中調(diào)用,也有用到一些新的解決方案如Promise相關(guān)的技術(shù)。 在異步編程中,還有一種常用的解決方案,它就是Generator生成器函數(shù)。顧...
摘要:執(zhí)行函數(shù)會返回一個遍歷器對象,每一次函數(shù)里面的都相當(dāng)一次遍歷器對象的方法,并且可以通過方法傳入自定義的來改變函數(shù)的行為。函數(shù)可以通過配合函數(shù)更輕松更優(yōu)雅的實(shí)現(xiàn)異步編程和控制流管理。它和構(gòu)造函數(shù)的不同點(diǎn)類的內(nèi)部定義的所有方法,都是不可枚舉的。 let const的命令 在ES6之前,聲明變量只能用var,var方式聲明變量其實(shí)是很不合理的,準(zhǔn)確的說,是因?yàn)镋S5里面沒有塊級作用域是很不合...
摘要:從開始,就在引入新功能,來幫助更簡單的方法來處理異步編程,幫助我們遠(yuǎn)離回調(diào)地獄。而則是為了更簡潔的使用而提出的語法,相比這種的實(shí)現(xiàn)方式,更為專注,生來就是為了處理異步編程。 從Promise開始,JavaScript就在引入新功能,來幫助更簡單的方法來處理異步編程,幫助我們遠(yuǎn)離回調(diào)地獄。 Promise是下邊要講的Generator/yield與async/await的基礎(chǔ),希望你已...
摘要:如果你還沒讀過上篇上篇和中篇并無依賴關(guān)系,您可以讀過本文之后再閱讀上篇,可戳面試篇寒冬求職季之你必須要懂的原生上小姐姐花了近百個小時(shí)才完成這篇文章,篇幅較長,希望大家閱讀時(shí)多花點(diǎn)耐心,力求真正的掌握相關(guān)知識點(diǎn)。 互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了裁員措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。 一年前,也許你搞清楚閉包,this,原型鏈,就能獲得...
摘要:如果你還沒讀過上篇上篇和中篇并無依賴關(guān)系,您可以讀過本文之后再閱讀上篇,可戳面試篇寒冬求職季之你必須要懂的原生上小姐姐花了近百個小時(shí)才完成這篇文章,篇幅較長,希望大家閱讀時(shí)多花點(diǎn)耐心,力求真正的掌握相關(guān)知識點(diǎn)。 互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了裁員措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。 一年前,也許你搞清楚閉包,this,原型鏈,就...
閱讀 970·2022-06-21 15:13
閱讀 1855·2021-10-20 13:48
閱讀 1039·2021-09-22 15:47
閱讀 1373·2019-08-30 15:55
閱讀 3128·2019-08-30 15:53
閱讀 526·2019-08-29 12:33
閱讀 721·2019-08-28 18:15
閱讀 3467·2019-08-26 13:58