新的數據結構Set相似於數組,可是成員的值都是惟一的,沒有重複的值。Set 自己是一個構造函數,用來生成 Set 數據結構。接受一個數組(或相似數組的對象)做爲參數,用來初始化。git
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 結構轉爲數組。es6
// 去除數組的重複成員 [...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是相等。
操做方法github
遍歷操做算法
因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。express
Set的遍歷順序就是插入順序。
經過該特性,當用Set保存一個回調函數列表,調用時就能保證按照添加順序調用。編程
Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values方法。擴展運算符(...)可用於 Set 結構。json
Set.prototype[Symbol.iterator] === Set.prototype.values // true
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數組自己。promise
WeakSet 結構有如下三個方法:數據結構
WeakSet 結構與 Set 相似,也是不重複的值的集合。可是,與Set不一樣的是:
WeakSet的以上特性決定WeakSet適合臨時存放一組對象,以及存放跟對象綁定的信息。只要這些對象在外部消失,它在 WeakMap 裏面的引用就會自動消失。
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。
Map 數據結構相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。Map做爲構造函數,能夠接受任何具備 Iterator 接口的數據結構做爲參數,好比數組。
//Map構造函數接受數組做爲參數,實際上執行的是下面的算法。 const items = [ ['name', '張三'], ['title', 'Author'] ]; const map = new Map(); items.forEach( ([key, value]) => map.set(key, value) );
只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵,由於Map 的鍵其實是跟內存地址綁定。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined
遍歷方法
Map 的遍歷順序就是插入順序。
Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法。Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。
WeakMap結構與Map結構相似,也是用於生成鍵值對的集合。可是
WeakMap的鍵名所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內。所以,只要所引用的對象的其餘引用都被清除,垃圾回收機制就會釋放該對象所佔用的內存。也就是說,一旦再也不須要,WeakMap 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
WeakMap結構有助於防止內存泄漏。
WeakMap沒有遍歷操做(即沒有key()、values()和entries()方法),也沒有size屬性,也不支持clear方法。所以,WeakMap只有四個方法可用:get()、set()、has()、delete()。
Promise對象特色:
缺點:
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的回調函數將會馬上執行。
the()方法返回一個新的Promise。所以能夠採用鏈式寫法。
promise.then(onFulfilled, onRejected); promise.then(function(value) { // success }, function(error) { // failure });
then方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise對象的狀態變爲Resolved時調用,第二個回調函數是Promise對象的狀態變爲Reject時調用。這兩個函數都接受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("ddd"); }); //ccc
上面代碼中,timeout函數中的 Promise狀態是resolve,可是第一個then中沒有對應的回調函數,所以第一個then返回的是resolve狀態的Promise。因此第二個then立馬被調用,輸出"ccc"。
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(iterable):方法返回一個新的異步的
promise,參數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永遠都是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對象。
Promise.resolve(value); Promise.resolve(promise); Promise.resolve(thenable);
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(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。所以,回調函數會當即執行。
Promise.reject(reason);
Promise.reject()方法的參數,會原封不動地做爲返回的新Promise的[[PromiseValue]]值,變成後續方法的參數。
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接口。所以,可經過(...)方便的將部署了Iterator接口的數據接口轉爲數組。
let arr = [...iterable];
任何接受數組做爲參數的場合,都調用了遍歷器接口。
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循環時:
ES6的數組、Set、Map均有如下方法(返回的都是遍歷器對象,與Object的entries、keys、values方法不一樣,Object返回的均是數組。):
for...of循環能正確識別字符串中的32位 UTF-16 字符。
可經過Array.from方法將相似數組的對象轉爲數組。
for...of循環能夠與break、continue和return配合使用,提供了遍歷全部數據結構的統一操做接口。
Generator 函數是一個普通函數,有如下特徵:
調用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"
遍歷器對象的next方法的運行邏輯:
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表達式若是用在另外一個表達式之中,必須放在圓括號裏面。若是用做函數參數或放在賦值表達式的右邊,能夠不加括號。
任意一個對象的Symbol.iterator方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的Symbol.iterator屬性。
Generator 函數執行後,返回一個遍歷器對象。該對象自己也具備Symbol.iterator屬性,執行後
返回自身。
function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true
yield表達式自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值。注意,因爲next方法的參數表示上一個yield表達式的返回值,因此第一次使用next方法時,不用帶參數。
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(); }
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函數。若該方法被調用時,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* 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 函數,則需在屬性前面加一個星號。
let obj = { * myGeneratorMethod() { ··· } }; //等同於 let obj = { myGeneratorMethod: function* () { // ··· } };
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 函數能夠暫停執行和恢復執行,這是它能封裝異步任務的根本緣由。整個 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函數返回一個 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 方法將會處理這個返回值;當異步函數拋出的是異常或者非法值時,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 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 函數的實現原理,就是將 Generator 函數和自動執行器,包裝在一個函數裏。
異步遍歷器的最大的語法特色,就是調用遍歷器的next方法,返回的是一個 Promise 對象。
asyncIterator .next() .then( ({ value, done }) => /* ... */ );
上面代碼中,asyncIterator是一個異步遍歷器,調用next方法之後,返回一個 Promise 對象。所以,可使用then方法指定,這個 Promise 對象的狀態變爲resolve之後的回調函數。回調函數的參數,則是一個具備value和done兩個屬性的對象,這個跟同步遍歷器是同樣的。
一個對象的同步遍歷器的接口,部署在Symbol.iterator屬性上面。一樣地,對象的異步遍歷器接口,部署在Symbol.asyncIterator屬性上面。無論是什麼樣的對象,只要它的Symbol.asyncIterator屬性有值,就表示應該對它進行異步遍歷。
for...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 函數就是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屬性。
constructor定義構造方法,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方法是類的默認方法,經過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 {}
類也可使用表達式的形式定義。
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指的是類,而不是實例。
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指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。
本質上,ES6的類只是ES5的構造函數的一層包裝,因此函數的許多特性都被Class繼承,包括name屬性。name屬性老是返回緊跟在class關鍵字後面的類名。
class Point {} Point.name // "Point"
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); }
Class同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。
類的繼承是按照下面的模式實現的。
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關鍵字後面能夠跟多種類型的值。
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(ColorPoint) === Point //true
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__屬性。
class Point{ } class ColorPoint extends Point{ constructor(){ super(); } } var p1 = new Point(); var p2 = new ColorPoint(); p2.__proto__.__proto__ === p1.__proto__ // true
原生構造函數是指語言內置的構造函數,一般用來生成數據結構。ECMAScript的原生構造函數大體有下面這些。
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內部可使用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屬性有對應的存值函數和取值函數,所以賦值和讀取行爲都被自定義了。
在一個方法前,加上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.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())。
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); // 輸出 false
參考自:ECMAScript 6 入門