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