摘要:以上的代碼對(duì)應(yīng)到就是調(diào)用父類的值得注意的是關(guān)鍵字表示父類的構(gòu)造函數(shù),相當(dāng)于的。舉個(gè)例子這是因?yàn)樽鳛闃?gòu)造函數(shù)的語法糖,同時(shí)有屬性和屬性,因此同時(shí)存在兩條繼承鏈。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。
前言
在上一篇 《 ES6 系列 Babel 是如何編譯 Class 的(上)》,我們知道了 Babel 是如何編譯 Class 的,這篇我們學(xué)習(xí) Babel 是如何用 ES5 實(shí)現(xiàn) Class 的繼承。
ES5 寄生組合式繼承function Parent (name) { this.name = name; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); var child1 = new Child("kevin", "18"); console.log(child1);
原型鏈?zhǔn)疽鈭D為:
關(guān)于寄生組合式繼承我們?cè)?《JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn)》 中介紹過。
引用《JavaScript高級(jí)程序設(shè)計(jì)》中對(duì)寄生組合式繼承的夸贊就是:
這種方式的高效率體現(xiàn)它只調(diào)用了一次 Parent 構(gòu)造函數(shù),并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的、多余的屬性。與此同時(shí),原型鏈還能保持不變;因此,還能夠正常使用 instanceof 和 isPrototypeOf。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式。ES6 extend
Class 通過 extends 關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。
以上 ES5 的代碼對(duì)應(yīng)到 ES6 就是:
class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 調(diào)用父類的 constructor(name) this.age = age; } } var child1 = new Child("kevin", "18"); console.log(child1);
值得注意的是:
super 關(guān)鍵字表示父類的構(gòu)造函數(shù),相當(dāng)于 ES5 的 Parent.call(this)。
子類必須在 constructor 方法中調(diào)用 super 方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悰]有自己的 this 對(duì)象,而是繼承父類的 this 對(duì)象,然后對(duì)其進(jìn)行加工。如果不調(diào)用 super 方法,子類就得不到 this 對(duì)象。
也正是因?yàn)檫@個(gè)原因,在子類的構(gòu)造函數(shù)中,只有調(diào)用 super 之后,才可以使用 this 關(guān)鍵字,否則會(huì)報(bào)錯(cuò)。
子類的 __proto__在 ES6 中,父類的靜態(tài)方法,可以被子類繼承。舉個(gè)例子:
class Foo { static classMethod() { return "hello"; } } class Bar extends Foo { } Bar.classMethod(); // "hello"
這是因?yàn)?Class 作為構(gòu)造函數(shù)的語法糖,同時(shí)有 prototype 屬性和 __proto__ 屬性,因此同時(shí)存在兩條繼承鏈。
(1)子類的 __proto__ 屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。
(2)子類 prototype 屬性的 __proto__ 屬性,表示方法的繼承,總是指向父類的 prototype 屬性。
class Parent { } class Child extends Parent { } console.log(Child.__proto__ === Parent); // true console.log(Child.prototype.__proto__ === Parent.prototype); // true
ES6 的原型鏈?zhǔn)疽鈭D為:
我們會(huì)發(fā)現(xiàn),相比寄生組合式繼承,ES6 的 class 多了一個(gè) Object.setPrototypeOf(Child, Parent) 的步驟。
繼承目標(biāo)extends 關(guān)鍵字后面可以跟多種類型的值。
class B extends A { }
上面代碼的 A,只要是一個(gè)有 prototype 屬性的函數(shù),就能被 B 繼承。由于函數(shù)都有 prototype 屬性(除了 Function.prototype 函數(shù)),因此 A 可以是任意函數(shù)。
除了函數(shù)之外,A 的值還可以是 null,當(dāng) extend null 的時(shí)候:
class A extends null { } console.log(A.__proto__ === Function.prototype); // true console.log(A.prototype.__proto__ === undefined); // trueBabel 編譯
那 ES6 的這段代碼:
class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 調(diào)用父類的 constructor(name) this.age = age; } } var child1 = new Child("kevin", "18"); console.log(child1);
Babel 又是如何編譯的呢?我們可以在 Babel 官網(wǎng)的 Try it out 中嘗試:
"use strict"; function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Parent = function Parent(name) { _classCallCheck(this, Parent); this.name = name; }; var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 調(diào)用父類的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child; }(Parent); var child1 = new Child("kevin", "18"); console.log(child1);
我們可以看到 Babel 創(chuàng)建了 _inherits 函數(shù)幫助實(shí)現(xiàn)繼承,又創(chuàng)建了 _possibleConstructorReturn 函數(shù)幫助確定調(diào)用父類構(gòu)造函數(shù)的返回值,我們來細(xì)致的看一看代碼。
_inheritsfunction _inherits(subClass, superClass) { // extend 的繼承目標(biāo)必須是函數(shù)或者是 null if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } // 類似于 ES5 的寄生組合式繼承,使用 Object.create,設(shè)置子類 prototype 屬性的 __proto__ 屬性指向父類的 prototype 屬性 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 設(shè)置子類的 __proto__ 屬性指向父類 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
關(guān)于 Object.create(),一般我們用的時(shí)候會(huì)傳入一個(gè)參數(shù),其實(shí)是支持傳入兩個(gè)參數(shù)的,第二個(gè)參數(shù)表示要添加到新創(chuàng)建對(duì)象的屬性,注意這里是給新創(chuàng)建的對(duì)象即返回值添加屬性,而不是在新創(chuàng)建對(duì)象的原型對(duì)象上添加。
舉個(gè)例子:
// 創(chuàng)建一個(gè)以另一個(gè)空對(duì)象為原型,且擁有一個(gè)屬性 p 的對(duì)象 const o = Object.create({}, { p: { value: 42 } }); console.log(o); // {p: 42} console.log(o.p); // 42
再完整一點(diǎn):
const o = Object.create({}, { p: { value: 42, enumerable: false, // 該屬性不可寫 writable: false, configurable: true } }); o.p = 24; console.log(o.p); // 42
那么對(duì)于這段代碼:
subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
作用就是給 subClass.prototype 添加一個(gè)可配置可寫不可枚舉的 constructor 屬性,該屬性值為 subClass。
_possibleConstructorReturn函數(shù)里是這樣調(diào)用的:
var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));
我們簡化為:
var _this = _possibleConstructorReturn(this, Parent.call(this, name));
_possibleConstructorReturn 的源碼為:
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
在這里我們判斷 Parent.call(this, name) 的返回值的類型,咦?這個(gè)值還能有很多類型?
對(duì)于這樣一個(gè) class:
class Parent { constructor() { this.xxx = xxx; } }
Parent.call(this, name) 的值肯定是 undefined??墒侨绻覀?cè)?constructor 函數(shù)中 return 了呢?比如:
class Parent { constructor() { return { name: "kevin" } } }
我們可以返回各種類型的值,甚至是 null:
class Parent { constructor() { return null } }
我們接著看這個(gè)判斷:
call && (typeof call === "object" || typeof call === "function") ? call : self;
注意,這句話的意思并不是判斷 call 是否存在,如果存在,就執(zhí)行 (typeof call === "object" || typeof call === "function") ? call : self
因?yàn)?&& 的運(yùn)算符優(yōu)先級(jí)高于 ? :,所以這句話的意思應(yīng)該是:
(call && (typeof call === "object" || typeof call === "function")) ? call : self;
對(duì)于 Parent.call(this) 的值,如果是 object 類型或者是 function 類型,就返回 Parent.call(this),如果是 null 或者基本類型的值或者是 undefined,都會(huì)返回 self 也就是子類的 this。
這也是為什么這個(gè)函數(shù)被命名為 _possibleConstructorReturn。
總結(jié)var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 調(diào)用父類的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child; }(Parent);
最后我們總體看下如何實(shí)現(xiàn)繼承:
首先執(zhí)行 _inherits(Child, Parent),建立 Child 和 Parent 的原型鏈關(guān)系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype) 和 Object.setPrototypeOf(Child, Parent)。
然后調(diào)用 Parent.call(this, name),根據(jù) Parent 構(gòu)造函數(shù)的返回值類型確定子類構(gòu)造函數(shù) this 的初始值 _this。
最終,根據(jù)子類構(gòu)造函數(shù),修改 _this 的值,然后返回該值。
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預(yù)計(jì)寫二十篇左右,旨在加深 ES6 部分知識(shí)點(diǎn)的理解,重點(diǎn)講解塊級(jí)作用域、標(biāo)簽?zāi)0?、箭頭函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實(shí)現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/99068.html
摘要:前言在了解是如何編譯前,我們先看看的和的構(gòu)造函數(shù)是如何對(duì)應(yīng)的。這是它跟普通構(gòu)造函數(shù)的一個(gè)主要區(qū)別,后者不用也可以執(zhí)行。該函數(shù)的作用就是將函數(shù)數(shù)組中的方法添加到構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型中,最后返回這個(gè)構(gòu)造函數(shù)。 前言 在了解 Babel 是如何編譯 class 前,我們先看看 ES6 的 class 和 ES5 的構(gòu)造函數(shù)是如何對(duì)應(yīng)的。畢竟,ES6 的 class 可以看作一個(gè)語法糖,...
摘要:年,很多人已經(jīng)開始接觸環(huán)境,并且早已經(jīng)用在了生產(chǎn)當(dāng)中。我們發(fā)現(xiàn),關(guān)鍵字會(huì)被編譯成構(gòu)造函數(shù),于是我們便可以通過來實(shí)現(xiàn)實(shí)例的生成。下一篇文章我會(huì)繼續(xù)介紹如何處理子類的并會(huì)通過一段函數(shù)橋梁,使得環(huán)境下也能夠繼承定義的。 2017年,很多人已經(jīng)開始接觸ES6環(huán)境,并且早已經(jīng)用在了生產(chǎn)當(dāng)中。我們知道ES6在大部分瀏覽器還是跑不通的,因此我們使用了偉大的Babel來進(jìn)行編譯。很多人可能沒有關(guān)心過,...
摘要:并且用驗(yàn)證了中一系列的實(shí)質(zhì)就是魔法糖的本質(zhì)。抽絲剝繭我們首先看的編譯結(jié)果這是一個(gè)自執(zhí)行函數(shù),它接受一個(gè)參數(shù)就是他要繼承的父類,返回一個(gè)構(gòu)造函數(shù)。 如果你已經(jīng)看過第一篇揭秘babel的魔法之class魔法處理,這篇將會(huì)是一個(gè)延伸;如果你還沒看過,并且也不想現(xiàn)在就去讀一下,單獨(dú)看這篇也沒有關(guān)系,并不存在理解上的障礙。 上一篇針對(duì)Babel對(duì)ES6里面基礎(chǔ)class的編譯進(jìn)行了分析。這一篇將...
摘要:第二部分源碼解析接下是應(yīng)用多個(gè)第二部分對(duì)于一個(gè)方法應(yīng)用了多個(gè),比如會(huì)編譯為在第二部分的源碼中,執(zhí)行了和操作,由此我們也可以發(fā)現(xiàn),如果同一個(gè)方法有多個(gè)裝飾器,會(huì)由內(nèi)向外執(zhí)行。有了裝飾器,就可以改寫上面的代碼。 Decorator 裝飾器主要用于: 裝飾類 裝飾方法或?qū)傩? 裝飾類 @annotation class MyClass { } function annotation(ta...
閱讀 2830·2019-08-30 15:53
閱讀 606·2019-08-29 17:22
閱讀 1286·2019-08-29 13:10
閱讀 2403·2019-08-26 13:45
閱讀 2880·2019-08-26 10:46
閱讀 3258·2019-08-26 10:45
閱讀 2605·2019-08-26 10:14
閱讀 542·2019-08-23 18:23