JavaScript 常見的繼承方式匯總
原型鏈機(jī)制:
在ECMAscript中描述了原型鏈的概念,并將原型鏈作為實(shí)現(xiàn)繼承的主要方法,其基本思想就是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。
構(gòu)造函數(shù)和原型還有實(shí)例之間的關(guān)系:
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象(prototype),原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針(constructor),而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針 ( __propto__ ) 。關(guān)系圖如下圖所示:
每一個(gè)Function都是Object基類的一個(gè)實(shí)例,所以每一個(gè)Function上都有一個(gè)__proto__指向了Object.prototype。
當(dāng)查找一個(gè)實(shí)例的屬性時(shí),會(huì)先從這個(gè)實(shí)例的自定義屬性上找,如果沒有的話通過(guò)__proto__去實(shí)例所屬類的原型上去找,如果還沒有的話再通過(guò)原型(原型也是對(duì)象,只要是對(duì)象就有__proto__屬性)的__proto__到Object的原型上去找,一級(jí)一級(jí)的找,如果沒有就undefined。
所以引用類型之間的繼承就是通過(guò)原型鏈機(jī)制實(shí)現(xiàn)的。
一.原型繼承
原型繼承:把父類的私有+公有的屬性和方法,都作為子類公有的屬性。
核心:不是把父類私有+公有的屬性克隆一份一模一樣的給子類的公有。他是通過(guò)__proto__建立和子類之間的原型鏈,當(dāng)子類的實(shí)例需要使用父類的屬性和方法的時(shí)候,可以通過(guò)__proto__一級(jí)級(jí)找上去使用?!?/p>
function Parent(){ this.x = 199; this.y = 299; } Parent.prototype.say = function(){ console.log('say') } function Child(){ this.g = 90; } Child.prototype = new Parent(); var p = new Parent(); var c = new Child(); console.dir(c)
實(shí)現(xiàn)的本質(zhì)是重寫了原型對(duì)象 ,通過(guò)將子類的原型指向了父類的實(shí)例,所以子類的實(shí)例就可以通過(guò)__proto__訪問到 Child.prototype 也就是 Parent的實(shí)例,這樣就可以訪問到父類的私有方法。然后再通過(guò)__proto__指向父類的prototype就可以獲得到父類原型上的方法。
這樣就做到了將父類的私有、公有方法和屬性都當(dāng)做子類的公有屬性。這樣就通過(guò)原型鏈實(shí)現(xiàn)了繼承。
但是別忘了默認(rèn)的原型,因?yàn)樗幸妙愋投际抢^承了Object的,所有說(shuō)子類也可以訪問到Object上的方法如toString() 、valueOf() 等。
結(jié)果如下圖所示:
有的時(shí)候我們需要在子類中添加新的方法或者是重寫父類的方法時(shí)候,切記一定要放到替換原型的語(yǔ)句之后。
function Parent(){ this.x = 199; this.y = 299; } Parent.prototype.say = function(){ console.log('say') } function Child(){ this.g = 90; } /*Child.prototype.Bs = function(){ console.log('Bs') }*/在這里寫子類的原型方法和屬性是沒用的因?yàn)闀?huì)改變?cè)偷闹赶?,所以?yīng)該放到重新指定之后 Child.prototype = new Parent(); Child.prototype.constructor=Child//由于重新修改了Child的原型導(dǎo)致默認(rèn)原型上的constructor丟失,我們需要自己添加上,其實(shí)沒啥用,加不加都一樣 Child.prototype.Bs = function(){ console.log('Bs') } Child.prototype.say = function(){ console.log('之后改的') } var p = new Parent(); var c = new Child(); console.dir(c) c.Bs() //Bs c.say() // 之后改的 p.say() //say 不影響父類實(shí)例訪問父類的方法
存在的問題:
1. 子類繼承父類的屬性和方法是將父類的私有屬性和公有方法都作為自己的公有屬性和方法,我們要清楚一件事情就是我們操作基本數(shù)據(jù)類型的時(shí)候操作的是值,在操作應(yīng)用數(shù)據(jù)類型的時(shí)候操作的是地址,如果說(shuō)父類的私有屬性中引用類型的屬性,那他被子類繼承的時(shí)候會(huì)作為公有屬性,這樣子類一操作這個(gè)屬性的時(shí)候,會(huì)影響到子類二。
2. 在創(chuàng)建子類的實(shí)例時(shí),不能向父類型的構(gòu)造函數(shù)中傳遞參數(shù)。應(yīng)該說(shuō)是沒有辦法在不影響所有對(duì)象實(shí)例的情況下,給父類的構(gòu)造函數(shù)傳遞參數(shù)。
所以在實(shí)際中很少單獨(dú)使用原型繼承。
二.call繼承
改變方法的this指向,同時(shí)執(zhí)行方法。 在子類構(gòu)造函數(shù)中父類.call(this) 可以將父類的私有變成子類的私有。
function Parent() { this.x = 100; this.y = 199; } Parent.prototype.fn = function() {} function Child() { this.d = 100; Parent.call(this); //構(gòu)造函數(shù)中的this就是當(dāng)前實(shí)例 } var p = new Parent(); var c = new Child(); console.log(p) //Parent {x: 100, y: 199} console.log(c) //Child {d: 100, x: 100, y: 199}
在子類的構(gòu)造函數(shù)中,改變父類的this指向,改變?yōu)樽宇?/span>的實(shí)例,同時(shí)運(yùn)行父類方法,這樣父類中的this.x就變成了子類的實(shí)例.x ,通過(guò)這種方法就可以繼承了父類的私有屬性,且只能繼承父類的私有屬性和方法。
三.冒充對(duì)象繼承
冒充對(duì)象繼承的原理是循環(huán)遍歷父類實(shí)例,然后父類實(shí)例的私有方法全部拿過(guò)來(lái)添加給子類實(shí)例。
function Parent(){ this.x = 100; } Parent.prototype.getX = function(){ console.log('getX') } function Child(){ var p = new Parent(); for(var attr in p){//for in 可以遍歷到原型上的公有自定義屬性 this[attr] = p[attr] } //以下代碼是只獲得到私有方法和屬性,如果不加這個(gè)的話就可以遍歷到所有方法和屬性 /*if(e.hasOwnProperty(attr)){ this[attr] = e[attr] } e.propertyIsEnumerable()*///可枚舉屬性==> 可以拿出來(lái)一一列舉的屬性 } var p = new Parent(); var c = new Child(); console.dir(c)
for in 可以遍歷到原型上的公有自定義屬性 ,所以他可以拿到私有和公有的屬性和方法,這個(gè)你可以遍歷私有和公有的,需要你加限制條件。但是如果不做hasOwnProperty判斷那么就是把父類的公有的和私有的都拿過(guò)來(lái)當(dāng)私有的。
四.混合繼承
就是將call繼承和原型繼承集合在一起,無(wú)論是私有的還是公有的都拿過(guò)來(lái)了。但是有個(gè)問題就是子類的原型上的多了一套父類私有屬性,但是不會(huì)產(chǎn)生問題。因?yàn)樽宇惖乃接袑傩砸灿幸惶紫嗤耐ㄟ^(guò)call繼承拿過(guò)來(lái)的。
function Parent(){ this.x=100; } Parent.prototype.getX = function(){} function Child(){ Parent.call(this); } Child.prototype = new Parent(); Child.prototype.constructor = Child; var p = new Parent(); var c = new Child(); console.log(c)//Child {x: 100}
存在的問題:
無(wú)論在什么情況下,都會(huì)調(diào)用兩次構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部,沒錯(cuò),子類型最終會(huì)包含父類型對(duì)象的全部實(shí)例屬性,但我們不得不在調(diào)用子類構(gòu)造函數(shù)時(shí)重寫這些屬性。
還有一種就是call+拷貝繼承
//混合繼承:call繼承+拷貝繼承 function extend(newEle,oldEle){ for(var attr in oldEle){ newEle[attr]=oldEle[attr]; } } function F(){ this.x=100; this.showX=function(){} } F.prototype.getX=function(){}; F.prototype.getX1=function(){}; var f1=new F; console.dir(f1) function S(){ F.call(this)//call繼承 } extend(S.prototype, F.prototype);//拷貝繼承 S.prototype.cc=function(){ } var p1=new S; console.dir(p1);
這種方式使用call繼承將父類的私有方法繼承過(guò)來(lái),使用for in 拷貝將父類的公有屬性和方法繼承過(guò)來(lái),比較實(shí)用。
五.中間件繼承
中間件繼承就是通過(guò)原型鏈的機(jī)制,子類的prototype.__proto__本來(lái)應(yīng)該是直接指向Object.prototype。
從父類的原型上的__proto__也可以到Object.prototype,在父類.prototype上停留了下,父類.prototype就是一個(gè)中間件,所以子類可以繼承到父類的公有方法當(dāng)做自己的公有方法。
function Parent(){ this.x = 100; } Parent.prototype.getX = function(){} function Child(){ } Child.prototype.__proto__ = Parent.prototype; var p = new Parent(); var c = new Child() console.log(c)
六.寄生組合式繼承
寄生式組合: call繼承+Object.create();
所謂寄生組合式繼承就是通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混合形式來(lái)繼承方法。
基本思路是不必為了指定子類的原型而調(diào)用父類的構(gòu)造函數(shù),我們所需要的就是父類型原型的一個(gè)副本。
本質(zhì)上,就是使用寄生式繼承父類的原型,然后再將結(jié)果指定給子類的原型。
function F(){ this.x=100; } F.prototype.showX=function(){}; function S(){ this.y = 200 F.call(this)//只繼承了私有的; } function inheritPrototype(subType,superType){ var prototype = Object.create(superType.prototype);//創(chuàng)建對(duì)象 prototype.constructor = subType;//增強(qiáng)對(duì)象 subType.prototype = prototype;//指定對(duì)象 } inheritPrototype(S,F) var p1=new S; console.dir(p1)
1、第一步是創(chuàng)建父類型原型的一個(gè)副本。
2、第二步是為創(chuàng)建的副本增加constructor屬性,從而彌補(bǔ)了因?yàn)橹貙懺投サ哪J(rèn)的constructor屬性。
3、第三步是將創(chuàng)建的對(duì)象賦值給子類型的原型。
這個(gè)例子的高效率體現(xiàn)在他只調(diào)用了一次SuperType 構(gòu)造函數(shù),并且因此避免了在SubType.prototype上面創(chuàng)建不必要的、多余的屬性。與此同時(shí)原型鏈還能保持不變,所以可以正常使用instanceof 和 isPrototypeOf() ,所以寄生組合繼承是引用類型最理想的繼承方法。
七.class繼承
class 可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過(guò)修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。
class Father{ constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } class Son extends Father{ constructor(x,y,color){ super(x,y); // 調(diào)用父類的constructor(x, y) this.color = color; } toString() { console.log( super.toString()+this.color); // 調(diào)用父類的toString() } } let son = new Son(3,4,'red'); son.toString();//結(jié)果為(3,4)red
上面代碼定義了一個(gè)Son類,該類通過(guò)extends關(guān)鍵字,繼承了Father類的所有屬性和方法。
上面代碼中,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字,它在這里表示父類的構(gòu)造函數(shù),用來(lái)新建父類的this對(duì)象。
子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇愖约旱膖his對(duì)象,必須先通過(guò)父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類就得不到this對(duì)象。
以上就是JavaScript 常見的繼承方式匯總的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 繼承方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS教程:window.location使用方法的區(qū)別介紹
這篇文章介紹了window.location使用方法的區(qū)別,有需要的朋友可以參考一下2013-10-10CountUp.js數(shù)字滾動(dòng)插件使用方法詳解
這篇文章主要為大家詳細(xì)介紹了CountUp.js數(shù)字滾動(dòng)插件的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10layui 上傳插件 帶預(yù)覽 非自動(dòng)上傳功能的實(shí)例(非常實(shí)用)
今天小編就為大家分享一篇layui 上傳插件 帶預(yù)覽 非自動(dòng)上傳功能的實(shí)例(非常實(shí)用),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09Javascript如何判斷數(shù)據(jù)類型和數(shù)組類型
這篇文章主要介紹Javascript如何判斷數(shù)據(jù)類型和數(shù)組類型,通俗易懂,需要的朋友可以參考下。2016-06-06基于Bootstrap的UI擴(kuò)展 StyleBootstrap
這篇文章主要為大家詳細(xì)介紹了基于Bootstrap的UI擴(kuò)展: StyleBootstrap,感興趣的小伙伴們可以參考一下2016-06-06