深入JS繼承
前言
對(duì)于靈活的js而言,繼承相比于java等語(yǔ)言,繼承實(shí)現(xiàn)方式可謂百花齊放。方式的多樣就意味著知識(shí)點(diǎn)繁多,當(dāng)然也是面試時(shí)繞不開(kāi)的點(diǎn)。撇開(kāi)ES6 class不談,傳統(tǒng)的繼承方式你知道幾種?每種實(shí)現(xiàn)原理是什么,優(yōu)劣點(diǎn)能談?wù)剢?。這里就結(jié)合具體例子,按照漸進(jìn)式的思路來(lái)看看繼承的發(fā)展。
準(zhǔn)備
談到j(luò)s繼承之前先回顧下js 實(shí)例化對(duì)象的實(shí)現(xiàn)方式。
構(gòu)造函數(shù)是指可以通過(guò)new 來(lái)實(shí)例化對(duì)象的函數(shù),目的就是為了復(fù)用,避免每次都手動(dòng)聲明對(duì)象實(shí)例。
new 簡(jiǎn)單實(shí)現(xiàn)如下:
function my_new(func){ var obj = {} obj._proto_ = func.prototype // 修改原型鏈指向,拼接至func原型鏈 func.call(obj) // 實(shí)例屬性賦值 return obj }
由上可以看出,通過(guò)構(gòu)造函數(shù)調(diào)用,可以將實(shí)例屬性賦值到目標(biāo)對(duì)象上。
如此可以推想,子類中調(diào)用父類構(gòu)造函數(shù)同樣可以達(dá)到繼承的目的。
這就提供了js繼承的一種思路,即通過(guò)構(gòu)造函數(shù)調(diào)用。
至于原型屬性,就是通過(guò)修改原型指向,來(lái)實(shí)現(xiàn)原型屬性的共享。
那么繼承時(shí)同樣也可以通過(guò)該方式進(jìn)行。
總結(jié)
基于構(gòu)造函數(shù)和原型鏈兩種特性,結(jié)合js語(yǔ)言的靈活性。
繼承的實(shí)現(xiàn)方式雖然繁多萬(wàn)變也不離其宗
繼承的n種方式
原型式繼承
定義:這種繼承借助原型并基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不用創(chuàng)建自定義類型的方式稱為原型式繼承。
直接看代碼更清晰:
function createObj(o) { function F() { } F.prototype = o; return new F(); } var parent = { name: 'trigkit4', arr: ['brother', 'sister', 'baba'] }; var child1 = createObj(parent);
該方式表面上看基于對(duì)象創(chuàng)建,不需要構(gòu)造函數(shù)(當(dāng)然實(shí)際構(gòu)造函數(shù)被封裝起來(lái)罷了)。只借助了原型對(duì)象,所以名稱為原型式繼承。
缺點(diǎn):
比較明顯優(yōu)良者
無(wú)法復(fù)用該繼承,每個(gè)子類的實(shí)例,都要走完整的createObj流程。
對(duì)于子類對(duì)象
因?yàn)闃?gòu)造函數(shù)封裝createObj中,對(duì)其而言,沒(méi)有構(gòu)造函數(shù)。由此造成無(wú)法初始化時(shí)傳參。
補(bǔ)充:其中 createObj 就是我們ES6中常用的Object.create(),不過(guò)Object.create進(jìn)行了完善,允許額外參數(shù)來(lái)完善了。
解決思路:
既然提到?jīng)]有構(gòu)造函數(shù)導(dǎo)致了問(wèn)題,那么大膽猜測(cè),更進(jìn)一步就是涉及了構(gòu)造函數(shù)的原型鏈繼承了。
原型鏈?zhǔn)嚼^承
定義:為了讓子類繼承父類的屬性(也包括方法),首先需要定義一個(gè)構(gòu)造函數(shù)。然后,將父類的新實(shí)例賦值給構(gòu)造函數(shù)的原型。
function Parent() { this.name = 'mike'; } function Child() { this.age = 12; } Child.prototype = new Parent(); child.prototype.contructor = child // 原型屬性被覆蓋,所以要修正回來(lái)。 var child1 = new Child();
也就是直接修改子類的原型對(duì)象指父構(gòu)造函數(shù)的實(shí)例,這樣把父類的實(shí)例屬性和原型屬性都掛到自己原型鏈上。
缺點(diǎn)
Child.prototype = new Parent() ,那么子函數(shù)自身的原型屬性就被覆蓋了,如果需要就要在后面補(bǔ)充。
子對(duì)象實(shí)例化時(shí),無(wú)法向父類構(gòu)造函數(shù)傳遞參數(shù)。
例如在new Child()執(zhí)行的時(shí)候,想要去覆蓋name,只能在Child.prototype = new Parent()時(shí)。 是我們?cè)趎ew Child()的時(shí)候統(tǒng)一傳參初始化是更常規(guī)需求。
解決思路
如何在子類初始化時(shí),調(diào)用父類構(gòu)造函數(shù)。結(jié)合前面的基礎(chǔ),答案也呼之欲出。
借用構(gòu)造函數(shù)(類式繼承)
類式繼承:是在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型的構(gòu)造函數(shù)。
思路比較清晰,由問(wèn)題驅(qū)動(dòng)。
既然原型鏈?zhǔn)阶宇惒荒芟蚋割悅鲄⒌膯?wèn)題,那么在子類初始化是調(diào)用父類不就滿足目的了。
示例如下:
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) { // 調(diào)用父類 Parent.call(this, age); } var child1 = new Child(21);
這樣滿足了初始化時(shí)傳參的需求,但是問(wèn)題也比較明顯。
child1.run //undefined
問(wèn)題
父類原型屬性丟失
父類初始化只繼承了示例屬性,原型屬性在子類的原型鏈上丟失
解決思路
丟失的原因在于原型鏈沒(méi)有修改指向,那么修改下指向不就完了。
組合繼承
定義:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承
示例:
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) { // 調(diào)用父類構(gòu)造函數(shù) Parent.call(this, age); } Child.prototype = new Parent();//原型屬性繼承 Child.prototype.contructor = Child var child1 = new Child(21);
這樣問(wèn)題就避免了:
child1.run() // "mike,jack,smith are both21"
問(wèn)題
功能滿足之后,就該關(guān)注性能了。這種繼承方式問(wèn)題在于父類構(gòu)造函數(shù)執(zhí)行了兩次。
分別是:
function Child(age) { // 調(diào)用父類構(gòu)造函數(shù),第二次 Parent.call(this, age); } Child.prototype = new Parent();//修改原型鏈指向,第一次
解決思路
解決自然是取消一次構(gòu)造函數(shù)調(diào)用,要取消自然要分析這兩次執(zhí)行,功能上是否有重復(fù)。
第一次同樣繼承了實(shí)例和原型屬性,第二次執(zhí)行同樣繼承了父類的實(shí)例屬性。
因此第二次滿足對(duì)父類傳參的不可獲取性,因此只能思考能否第一次不調(diào)用父類構(gòu)造函數(shù),只繼承原型屬性。
答案自然是能,前面原型式繼承就是這個(gè)思路。
寄生組合式繼承
顧名思義,寄生指的是將繼承原型屬性的方法封裝在特定方法中,組合的是將構(gòu)造函數(shù)繼承組合起來(lái),補(bǔ)充原型式繼承的不足。
饒了點(diǎn),直接看:
function createObj(o) { function F() { } F.prototype = o; return new F(); } //繼承原型屬性 即原型式繼承 function create(parent, child) { var f = createObj(parent.prototype);//獲取原型對(duì)象 child.prototype = f child.prototype.constructor = child;//增強(qiáng)對(duì)象原型,即保持原有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);
這樣沿著發(fā)現(xiàn)問(wèn)題解決問(wèn)題的思路直到相對(duì)完善的繼承方式。至于ES的方式本篇就不涉及了。
結(jié)束語(yǔ)
唯有厚積,才能薄發(fā),想要心儀的offer,就得準(zhǔn)備充裕,夯實(shí)基礎(chǔ),切忌似是而非,道理我都懂就是答得不完全,這樣跟不懂差別也不太大。不算新的日子里立個(gè)flag,每周三個(gè)知識(shí)點(diǎn)回顧。要去相信,你若盛開(kāi)蝴蝶自來(lái)。
以上就是深入JS繼承的詳細(xì)內(nèi)容,更多關(guān)于深入JS繼承的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
優(yōu)雅而高效的JavaScript?try...catch語(yǔ)句詳解(js異常處理)
這篇文章主要給大家介紹了關(guān)于JavaScript中try...catch語(yǔ)句的相關(guān)資料,也就是js異常處理方法,try...catch是JavaScript中的錯(cuò)誤處理機(jī)制,它的作用是捕獲和處理可能發(fā)生的錯(cuò)誤,以避免程序崩潰,需要的朋友可以參考下2024-01-01js中的時(shí)間轉(zhuǎn)換—毫秒轉(zhuǎn)換成日期時(shí)間的示例代碼
本篇文章主要是對(duì)js中的時(shí)間轉(zhuǎn)換—毫秒轉(zhuǎn)換成日期時(shí)間的示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01用Javascript 和 CSS 實(shí)現(xiàn)腳注(Footnote)效果
腳注(Footnote)是向用戶提供更多信息的一個(gè)最佳途徑,也是主體信息的一個(gè)有效補(bǔ)充,常見(jiàn)于各種印刷書籍中。2009-09-09為什么TypeScript的Enum會(huì)出現(xiàn)問(wèn)題
TypeScript引入了很多靜態(tài)編譯語(yǔ)言的特性,今天有一個(gè)類型需要著重討論下,這就是enum,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06js中的escape及unescape函數(shù)的php實(shí)現(xiàn)代碼
js中的escape及unescape函數(shù)的php實(shí)現(xiàn)代碼...2007-09-09JS實(shí)現(xiàn)前端動(dòng)態(tài)分頁(yè)碼代碼實(shí)例
這篇文章主要介紹了JS實(shí)現(xiàn)前端動(dòng)態(tài)分頁(yè)碼代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06JS實(shí)現(xiàn)簡(jiǎn)單打字測(cè)試
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡(jiǎn)單打字測(cè)試,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06