對於靈活的js而言,繼承相比於java等語言,繼承實現方式可謂百花齊放。方式的多樣就意味着知識點繁多,固然也是面試時繞不開的點。撇開ES6 class不談,傳統的繼承方式你知道幾種?每種實現原理是什麼,優劣點能談談嗎。這裏就結合具體例子,按照漸進式的思路來看看繼承的發展。java
談到js繼承以前先回顧下js 實例化對象的實現方式。面試
構造函數是指能夠經過new 來實例化對象的函數,目的就是爲了複用,避免每次都手動聲明對象實例。bash
new 簡單實現以下:函數
function my_new(func){
var obj = {}
obj._proto_ = func.prototype // 修改原型鏈指向,拼接至func原型鏈
func.call(obj) // 實例屬性賦值
return obj
}
複製代碼
由上能夠看出,經過構造函數調用,能夠將實例屬性賦值到目標對象上。
如此能夠推想,子類中調用父類構造函數一樣能夠達到繼承的目的。
這就提供了js繼承的一種思路,即經過構造函數調用。性能
至於原型屬性,就是經過修改原型指向,來實現原型屬性的共享。
那麼繼承時一樣也能夠經過該方式進行。this
總結spa
基於構造函數和原型鏈兩種特性,結合js語言的靈活性。
繼承的實現方式雖然繁多萬變也不離其宗prototype
定義:這種繼承藉助原型並基於已有的對象建立新對象,同時還不用建立自定義類型的方式稱爲原型式繼承。code
直接看代碼更清晰:對象
function createObj(o) {
function F() { }
F.prototype = o;
return new F();
}
var parent = {
name: 'trigkit4',
arr: ['brother', 'sister', 'baba']
};
var child1 = createObj(parent);
複製代碼
該方式表面上看基於對象建立,不須要構造函數(固然實際構造函數被封裝起來罷了)。只借助了原型對象,因此名稱爲原型式繼承。
缺點 比較明顯優良者
解決思路 既然提到沒有構造函數致使了問題,那麼大膽猜想,更進一步就是涉及了構造函數的原型鏈繼承了。
定義:爲了讓子類繼承父類的屬性(也包括方法),首先須要定義一個構造函數。而後,將父類的新實例賦值給構造函數的原型。
function Parent() {
this.name = 'mike';
}
function Child() {
this.age = 12;
}
Child.prototype = new Parent();
child.prototype.contructor = child // 原型屬性被覆蓋,因此要修正回來。
var child1 = new Child();
複製代碼
也就是直接修改子類的原型對象指父構造函數的實例,這樣把父類的實例屬性和原型屬性都掛到本身原型鏈上。
缺點
解決思路 如何在子類初始化時,調用父類構造函數。結合前面的基礎,答案也呼之欲出。
類式繼承:是在子類型構造函數的內部調用超類型的構造函數。
思路比較清晰,由問題驅動。
既然原型鏈式子類不能向父類傳參的問題,那麼在子類初始化是調用父類不就知足目的了。
示例以下:
function Parent(age) {
this.name = ['mike', 'jack', 'smith'];
this.age = age;
}
Parent.prototype.run = function () {
return this.name + ' are both' + this.age;
};
function Child(age) {
// 調用父類
Parent.call(this, age);
}
var child1 = new Child(21);
複製代碼
這樣知足了初始化時傳參的需求,可是問題也比較明顯。
child1.run //undefined
複製代碼
問題
解決思路 丟失的緣由在於原型鏈沒有修改指向,那麼修改下指向不就完了。
定義:使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承
示例:
function Parent(age) {
this.name = ['mike', 'jack', 'smith'];
this.age = age;
}
Parent.prototype.run = function () {
return this.name + ' are both' + this.age;
};
function Child(age) {
// 調用父類構造函數
Parent.call(this, age);
}
Child.prototype = new Parent();//原型屬性繼承
Child.prototype.contructor = Child
var child1 = new Child(21);
複製代碼
這樣問題就避免了:
child1.run() // "mike,jack,smith are both21"
複製代碼
問題 功能知足以後,就該關注性能了。這種繼承方式問題在於父類構造函數執行了兩次。
分別是:
function Child(age) {
// 調用父類構造函數,第二次
Parent.call(this, age);
}
Child.prototype = new Parent();//修改原型鏈指向,第一次
複製代碼
解決思路 解決天然是取消一次構造函數調用,要取消天然要分析這兩次執行,功能上是否有重複。
第一次一樣繼承了實例和原型屬性,第二次執行一樣繼承了父類的實例屬性。
所以第二次知足對父類傳參的不可獲取性,所以只能思考可否第一次不調用父類構造函數,只繼承原型屬性。
答案天然是能,前面原型式繼承就是這個思路。
顧名思義,寄生指的是將繼承原型屬性的方法封裝在特定方法中,組合的是將構造函數繼承組合起來,補充原型式繼承的不足。
饒了點,直接看:
function createObj(o) {
function F() { }
F.prototype = o;
return new F();
}
//繼承原型屬性 即原型式繼承
function create(parent, child) {
var f = createObj(parent.prototype);//獲取原型對象
child.prototype = f
child.prototype.constructor = child;//加強對象原型,即保持原有constructor指向
}
function Parent(name) {
this.name = name;
this.arr = ['brother', 'sister', 'parents'];
}
Parent.prototype.run = function () {
return this.name;
};
function Child(name, age) {
// 示例屬性
Parent.call(this, name);
this.age = age;
}
// 原型屬性繼承寄生於該方法中
create(Parent.prototype,Child);
var child1 = new Child('trigkit4', 21);
複製代碼
這樣沿着發現問題解決問題的思路直到相對完善的繼承方式。至於ES的方式本篇就不涉及了。
惟有厚積,才能薄發,想要心儀的offer,就得準備充裕,夯實基礎,切忌似是而非,道理我都懂就是答得不徹底,這樣跟不懂差異也不太大。不算新的日子裏立個flag,每週三個知識點回顧。要去相信,你若怒放蝴蝶自來。