JS
系列暫定 27 篇,從基礎,到原型,到異步,到設計模式,到架構模式等,前端
本篇是 JS
系列中第 3 篇,文章主講 JS
繼承,包括原型鏈繼承、構造函數繼承、組合繼承、寄生組合繼承、原型式繼承、 ES6 繼承,以及 多繼承與 new 。git
先定義一個父類github
function SuperType () {
// 屬性
this.name = 'SuperType';
}
// 原型方法
SuperType.prototype.sayName = function() {
return this.name;
};
複製代碼
將父類的實例做爲子類的原型設計模式
// 父類
function SuperType () {
this.name = 'SuperType'; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
return this.name;
};
// 子類
function SubType () {
this.subName = "SubType"; // 子類屬性
};
SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例
// 這裏實例化一個 SuperType 時, 實際上執行了兩步
// 1,新建立的對象複製了父類構造函數內的全部屬性及方法
// 2,並將原型 __proto__ 指向了父類的原型對象
SubType.prototype.saySubName = function () { // 子類原型方法
return this.subName;
}
// 子類實例
let instance = new SubType();
// instanceof 經過判斷對象的 prototype 鏈來肯定對象是不是某個類的實例
instance instanceof SubType; // true
instance instanceof SuperType; // true
// 注意
SubType instanceof SuperType; // false
SubType.prototype instanceof SuperType ; // true
複製代碼
利用原型,讓一個引用類型繼承另外一個引用類型的屬性及方法架構
繼承了父類的模板,又繼承了父類的原型對象app
能夠在子類構造函數中,爲子類實例增長實例屬性。若是要新增原型屬性和方法,則必須放在 SubType.prototype = new SuperType('SubType');
這樣的語句以後執行。異步
沒法實現多繼承ide
來自原型對象的全部屬性被全部實例共享函數
// 父類
function SuperType () {
this.colors = ["red", "blue", "green"];
this.name = "SuperType";
}
// 子類
function SubType () {}
// 原型鏈繼承
SubType.prototype = new SuperType();
// 實例1
var instance1 = new SubType();
instance1.colors.push("blcak");
instance1.name = "change-super-type-name";
console.log(instance1.colors); // ["red", "blue", "green", "blcak"]
console.log(instance1.name); // change-super-type-name
// 實例2
var instance2 = new SubType();
console.log(instance2.colors); // ["red", "blue", "green", "blcak"]
console.log(instance2.name); // SuperType
複製代碼
注意:更改 SuperType
引用類型屬性時,會使 SubType
全部實例共享這一更新。基礎類型屬性更新則不會。post
建立子類實例時,沒法向父類構造函數傳參,或者說是,沒辦法在不影響全部對象實例的狀況下,向超類的構造函數傳遞參數
在子類型的構造函數內部調用父類型構造函數。
函數只不過是在特定環境中執行代碼的對象,因此這裏使用 apply/call 來實現。
使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(沒用到原型)
// 父類
function SuperType (name) {
this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
return this.name;
};
// 子類
function SubType () {
// 調用 SuperType 構造函數
SuperType.call(this, 'SuperType'); // 在子類構造函數中,向父類構造函數傳參
// 爲了保證子父類的構造函數不會重寫子類的屬性,須要在調用父類構造函數後,定義子類的屬性
this.subName = "SubType"; // 子類屬性
};
// 子類實例
let instance = new SubType(); // 運行子類構造函數,並在子類構造函數中運行父類構造函數,this綁定到子類
複製代碼
解決了1中子類實例共享父類引用對象的問題,實現多繼承,建立子類實例時,能夠向父類傳遞參數
顧名思義,組合繼承就是將原型鏈繼承與構造函數繼承組合在一塊兒,從而發揮二者之長的一種繼承模式。
使用原型鏈繼承使用對原型屬性和方法的繼承,經過構造函數繼承來實現對實例屬性的繼承。這樣既能經過在原型上定義方法實現函數複用,又能保證每一個實例都有本身的屬性。
經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用
// 父類
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
return this.name;
};
// 子類
function SubType (name, subName) {
// 調用 SuperType 構造函數
SuperType.call(this, name); // ----第二次調用 SuperType----
this.subName = subName;
};
// ----第一次調用 SuperType----
SubType.prototype = new SuperType(); // 重寫原型對象,代之以一個新類型的實例
SubType.prototype.constructor = SubType; // 組合繼承須要修復構造函數指向
SubType.prototype.saySubName = function () { // 子類原型方法
return this.subName;
}
// 子類實例
let instance = new SubType('An', 'sisterAn')
instance.colors.push('black')
console.log(instance.colors) // ["red", "blue", "green", "black"]
instance.sayName() // An
instance.saySubName() // sisterAn
let instance1 = new SubType('An1', 'sisterAn1')
console.log(instance1.colors) // ["red", "blue", "green"]
instance1.sayName() // An1
instance1.saySubName() // sisterAn1
複製代碼
第一次調用 SuperType
構造函數時,SubType.prototype
會獲得兩個屬性name
和colors
;當調用 SubType
構造函數時,第二次調用 SuperType
構造函數,這一次又在新對象屬性上建立了 name
和colors
,這兩個屬性就會屏蔽原型對象上的同名屬性。
// instanceof:instance 的原型鏈是針對 SuperType.prototype 進行檢查的
instance instanceof SuperType // true
instance instanceof SubType // true
// isPrototypeOf:instance 的原型鏈是針對 SuperType 自己進行檢查的
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
複製代碼
彌補了方式2的缺陷,能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法,不存在引用屬性共享問題,可傳參,可複用
在組合繼承中,調用了兩次父類構造函數,這裏 經過經過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
借用 構造函數 繼承 屬性 ,經過 原型鏈的混成形式 來繼承 方法
// 父類
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
return this.name;
};
// 子類
function SubType (name, subName) {
// 調用 SuperType 構造函數
SuperType.call(this, name); // ----第二次調用 SuperType,繼承實例屬性----
this.subName = subName;
};
// ----第一次調用 SuperType,繼承原型屬性----
SubType.prototype = Object.create(SuperType.prototype)
SubType.prototype.constructor = SubType; // 注意:加強對象
let instance = new SubType('An', 'sisterAn')
複製代碼
SuperType
構造函數,只建立一份父類屬性instanceof
與 isPrototypeOf
實現思路就是將子類的原型設置爲父類的原型
// 父類
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父類屬性
}
SuperType.prototype.sayName = function () { // 父類原型方法
return this.name;
};
/** 第一步 */
// 子類,經過 call 繼承父類的實例屬性和方法,不能繼承原型屬性/方法
function SubType (name, subName) {
SuperType.call(this, name); // 調用 SuperType 的構造函數,並向其傳參
this.subName = subName;
}
/** 第二步 */
// 解決 call 沒法繼承父類原型屬性/方法的問題
// Object.create 方法接受傳入一個做爲新建立對象的原型的對象,建立一個擁有指定原型和若干個指定屬性的對象
// 經過這種方法指定的任何屬性都會覆蓋原型對象上的同名屬性
SubType.prototype = Object.create(SuperType.prototype, {
constructor: { // 注意指定 SubType.prototype.constructor = SubType
value: SubType,
enumerable: false,
writable: true,
configurable: true
},
run : {
value: function(){ // override
SuperType.prototype.run.apply(this, arguments);
// call super
// ...
},
enumerable: true,
configurable: true,
writable: true
}
})
/** 第三步 */
// 最後:解決 SubType.prototype.constructor === SuperType 的問題
// 這裏,在上一步已經指定,這裏不須要再操做
// SubType.prototype.constructor = SubType;
var instance = new SubType('An', 'sistenAn')
複製代碼
若是但願能 多繼承 ,可以使用 混入 的方式
// 父類 SuperType
function SuperType () {}
// 父類 OtherSuperType
function OtherSuperType () {}
// 多繼承子類
function AnotherType () {
SuperType.call(this) // 繼承 SuperType 的實例屬性和方法
OtherSuperType.call(this) // 繼承 OtherSuperType 的實例屬性和方法
}
// 繼承一個類
AnotherType.prototype = Object.create(SuperType.prototype);
// 使用 Object.assign 混合其它
Object.assign(AnotherType.prototype, OtherSuperType.prototype);
// Object.assign 會把 OtherSuperType 原型上的函數拷貝到 AnotherType 原型上,使 AnotherType 的全部實例均可用 OtherSuperType 的方法
// 從新指定 constructor
AnotherType.prototype.constructor = AnotherType;
AnotherType.prototype.myMethod = function() {
// do a thing
};
let instance = new AnotherType()
複製代碼
SuperType.call
繼承實例屬性方法Object.create()
來繼承原型屬性與方法SubType.prototype.constructor
的指向首先,實現一個簡單的 ES6 繼承:
class People {
constructor(name) {
this.name = name
}
run() { }
}
// extends 至關於方法的繼承
// 替換了上面的3行代碼
class Man extends People {
constructor(name) {
// super 至關於屬性的繼承
// 替換了 People.call(this, name)
super(name)
this.gender = '男'
}
fight() { }
}
複製代碼
extends
繼承的核心代碼以下,其實現和上述的寄生組合式繼承方式同樣
function _inherits(subType, superType) {
// 建立對象,Object.create 建立父類原型的一個副本
// 加強對象,彌補因重寫原型而失去的默認的 constructor 屬性
// 指定對象,將新建立的對象賦值給子類的原型 subType.prototype
subType.prototype = Object.create(superType && superType.prototype, {
constructor: { // 重寫 constructor
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
複製代碼
注: 考慮到JavaScript的工做方式,因爲原型鏈等特性的存在,在不一樣對象之間功能的共享一般被叫作 委託 - 特殊的對象將功能委託給通用的對象類型完成。這也許比將其稱之爲繼承更爲貼切,由於「被繼承」了的功能並無被拷貝到正在「進行繼承」的對象中,相反它仍存在於通用的對象中。
想看更過系列文章,點擊前往 github 博客主頁
1. ❤️玩得開心,不斷學習,並始終保持編碼。👨💻
2. 若有任何問題或更獨特的看法,歡迎評論或直接聯繫瓶子君(公衆號回覆 123 便可)!👀👇
3. 👇歡迎關注:前端瓶子君,每日更新!👇