JavaScript進階教程之非extends的組合繼承詳解
前言
繼承也是面向?qū)ο蟮奶匦灾唬窃?ES6 版本之前是沒有 extends 去實現(xiàn)繼承的,我們只能通過 構(gòu)造函數(shù) 和 原型對象 來實現(xiàn)繼承,其中分別為構(gòu)造函數(shù)來繼承屬性,原型對象來繼承方法,這種繼承模式被稱為 組合繼承
一:call() 的作用與使用
在開始講解組合繼承前我們先來了解一下 call() 方法,call() 方法可以改變 this 的指向,也可以調(diào)用函數(shù)等等,最主要的還是其改變指向的作用
| 語法格式 | call( 目標this指向,參數(shù)1,參數(shù)2 ......) |
1.1 使用 call() 來調(diào)用函數(shù)
call() 可以拿來直接用來調(diào)用函數(shù)
<script>
function eat(){
console.log('我在吃午飯');
}
eat.call()
</script>
1.2 使用 call() 來改變 this 的指向
call() 的第一個參數(shù)為你要改變的 this 的指向,這里的 this 指的是 call 的調(diào)用者,此處函數(shù)調(diào)用不指定的話即指向 window,指定讓其指向新創(chuàng)建的對象 obj,只需要讓其第一個參數(shù)為 obj 對象即可,所以結(jié)果應該是第一個為 window,第二個為 obj 對象
<script>
function eat(){
console.log(this);
}
var obj={
'name':'小明',
'age':18
}
eat.call()
eat.call(obj)
</script>
二:利用構(gòu)造函數(shù)繼承父屬性
我們已經(jīng)知道組合繼承是由構(gòu)造函數(shù)和原型對象一起來實現(xiàn)的,其中構(gòu)造函數(shù)實現(xiàn)的是屬性的繼承,原型對象實現(xiàn)的是方法的繼承,這版塊就走進利用父構(gòu)造函數(shù)完成屬性的繼承
2.1 實現(xiàn)過程
其實現(xiàn)非常容易,只需要在子構(gòu)造函數(shù)中,使用 call 調(diào)用父構(gòu)造函數(shù)(將其當做普通函數(shù)調(diào)用),其中在 call 方法中更改父構(gòu)造函數(shù)中的 this 指向,由于 call 方法是在子構(gòu)造函數(shù)中調(diào)用的,所以此處當做參數(shù)的 this 代表父構(gòu)造函數(shù)中的 this 指向子構(gòu)造函數(shù)的實例化對象,并傳參進去,所以相當于給子構(gòu)造函數(shù)的實例化對象添加了屬性并賦值
<script>
//聲明父構(gòu)造函數(shù)
function Father(uname,uage,utel,sex){
this.uname=uname;
this.uage=uage;
this.utel=utel;
this.sex=sex;
}
//聲明子構(gòu)造函數(shù),但是想繼承父類的uname,uage,utel等等屬性的賦值操作
function Son(uname,uage,utel,sex){
Father.call(this,uname,uage,utel,sex)
}
var son1=new Son('張三',19,12345,'男')
console.log(son1);
</script>
2.1 實現(xiàn)過程分析
- 首先在子構(gòu)造函數(shù)中使用 call 調(diào)用了父構(gòu)造函數(shù),并傳參給 call 的參數(shù),其中第一個參數(shù)為 this 指向的改變,其余為帶入的屬性值參數(shù)
- 我們知道構(gòu)造函數(shù)中的 this 指向其實例化對象,所以本身父構(gòu)造函數(shù)的 this 應該指向父構(gòu)造函數(shù)的實例化對象,而此處 call 方法調(diào)用在子構(gòu)造函數(shù)中,所以參數(shù)的指向更改為指向子構(gòu)造函數(shù)的實例化對象
- 此處子構(gòu)造函數(shù)的實例化對象就是 son1,所以父構(gòu)造函數(shù)中的 this 指向的均是 son1,
- 所以就給 son1 添加并賦值了 uname,uage 等等屬性
三:利用原型對象繼承父方法
組合繼承的最后一版塊,利用原型對象來繼承方法,此處我們說明的是存放在構(gòu)造函數(shù)的原型對象里的公共方法的繼承
3.1 繼承父方法的錯誤演示
錯誤的繼承就是直接將父親的原型對象賦值給子的原型對象,這樣確實也可行,但是如果給子原型對象添加子類特有的方法,那父原型對象也會加上這個方法
<script>
//聲明父構(gòu)造函數(shù)
function Father(uname,uage){
this.uname=uname;
this.uage=uage;
}
Father.prototype.money=function(){
console.log('我有很多錢');
}
//聲明子構(gòu)造函數(shù)
Son.prototype=Father.prototype;
function Son(uname,uage){
Father.call(this,uname,uage)
}
var father1=new Father('爸爸',40)
var son1=new Son('兒子',19)
console.log(father1);
console.log(son1);
</script>我們可以發(fā)現(xiàn)父子的原型對象中確實都有了這個方法,證明確實這個辦法是行得通的

但是其也有問題存在,當我們想給子原型對象單獨添加其特有的方法時,就會出問題
上述問題給子原型對象添加特有方法的錯誤示例:
<script>
//聲明父構(gòu)造函數(shù)
function Father(uname,uage){
this.uname=uname;
this.uage=uage;
}
Father.prototype.money=function(){
console.log('我有很多錢');
}
//聲明子構(gòu)造函數(shù)
Son.prototype=Father.prototype;
Son.prototype.school=function(){
console.log('我去上學了');
}
function Son(uname,uage){
Father.call(this,uname,uage)
}
var father1=new Father('爸爸',40)
var son1=new Son('兒子',19)
console.log(father1);
console.log(son1);
</script>我們發(fā)現(xiàn),我們確實給兒子添加上了兒子特有的方法,但是,父親的原型對象內(nèi)也加上了這個方法,這并不滿足我們的預期,原因分析如下

問題原因
問題就在于我們的原型對象也是對象,對象是引用數(shù)據(jù)類型,引用數(shù)據(jù)類型的對象本質(zhì)是在堆內(nèi)存存放,是不能直接訪問的,其訪問是通過棧內(nèi)存上的引用地址來找到去訪問,而我們此處采用的等號賦值的方式,實際上是將其在棧內(nèi)存上的引用地址拷貝過去了,二者指向了同一塊內(nèi)存空間,所以更改子原型對象,父原型對象也改變了

3.2 繼承父方法的正確做法
正確的做法是讓其子原型對象對象等于父實例化對象 Son.prototype=new Father(),其實我感覺有種高內(nèi)聚低耦合的韻味,減少了直接聯(lián)系從而解決問題

<script>
//聲明父構(gòu)造函數(shù)
function Father(uname,uage){
this.uname=uname;
this.uage=uage;
}
Father.prototype.money=function(){
console.log('我有很多錢');
}
//聲明子構(gòu)造函數(shù)
Son.prototype=new Father();
Son.prototype.school=function(){
console.log('我去上學了');
}
function Son(uname,uage){
Father.call(this,uname,uage)
}
var father1=new Father('爸爸',40)
var son1=new Son('兒子',19)
console.log(father1);
console.log(son1);
</script>問題得以解決,子原型對象有了自己特有的方法,并且也繼承了父親原型對象中的方法

3.2 繼承父方法的注意事項
我們以 Son.prototype=new Father() 這種方法繼承,看似已經(jīng)天衣無縫,其實我們早就說過,采用等號賦值的方法會造成原型對象被覆蓋,里面的構(gòu)造函數(shù) constructor 會被覆蓋掉,需要我們手動返回,所以七千萬要記得手動返回 constructor
<script>
//聲明父構(gòu)造函數(shù)
function Father(uname,uage){
this.uname=uname;
this.uage=uage;
}
Father.prototype.money=function(){
console.log('我有很多錢');
}
//聲明子構(gòu)造函數(shù)
Son.prototype=new Father();
Son.prototype.constructor=Son; //手動返回構(gòu)造函數(shù)constructor
Son.prototype.school=function(){
console.log('我去上學了');
}
function Son(uname,uage){
Father.call(this,uname,uage)
}
var father1=new Father('爸爸',40)
var son1=new Son('兒子',19)
console.log(father1);
console.log(son1);
console.log(Son.prototype.constructor);
</script>
補充:缺點
就是 “對象原型繼承的this對象+prototype對象,都在對象的原型上 suber1._ proto _”,suber1._ proto _上的引用屬性任然是共享;
所以就有了我們看到的一句勸告:盡量把屬性定義在構(gòu)造函數(shù)內(nèi),為了方便繼承吧;
還有就是實例一個對象,執(zhí)行了兩次Super()
總結(jié)
到此這篇關(guān)于JavaScript進階教程之非extends的組合繼承的文章就介紹到這了,更多相關(guān)js非extends的組合繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實現(xiàn)AOP詳解(面向切面編程,裝飾者模式)
下面小編就為大家分享一篇JavaScript實現(xiàn)AOP的方法(面向切面編程,裝飾者模式),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
javascript省市區(qū)三級聯(lián)動下拉框菜單實例演示
這篇文章主要為大家詳細介紹了javascript實現(xiàn)省市區(qū)三級聯(lián)動下拉框菜單很詳細的代碼,解決了大家實現(xiàn)javascript省市區(qū)三級聯(lián)動下拉框菜單的問題,感興趣的小伙伴們可以參考一下2015-11-11
js prototype 格式化數(shù)字 By shawl.qiu
js prototype 格式化數(shù)字 By shawl.qiu...2007-04-04
js?Cannot?set?properties?of?null(setting?‘onclick‘)問題分
今天增加功能的時候,提示Uncaught?TypeError:?Cannot?set?properties?of?null?(setting?onclick)問題分享下,需要的朋友可以參考下2023-06-06
關(guān)于javascript原型的修改與重寫(覆蓋)差別詳解
下面小編就為大家?guī)硪黄P(guān)于javascript原型的修改與重寫(覆蓋)差別詳解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08

