JavaScript進階教程之非extends的組合繼承詳解
前言
繼承也是面向對象的特性之一,但是在 ES6 版本之前是沒有 extends 去實現繼承的,我們隻能通過 構造函數 和 原型對象 來實現繼承,其中分別為構造函數來繼承屬性,原型對象來繼承方法,這種繼承模式被稱為 組合繼承
一:call() 的作用與使用
在開始講解組合繼承前我們先來瞭解一下 call() 方法,call() 方法可以改變 this 的指向,也可以調用函數等等,最主要的還是其改變指向的作用
語法格式 | call( 目標this指向,參數1,參數2 ……) |
1.1 使用 call() 來調用函數
call() 可以拿來直接用來調用函數
<script> function eat(){ console.log('我在吃午飯'); } eat.call() </script>
1.2 使用 call() 來改變 this 的指向
call() 的第一個參數為你要改變的 this 的指向,這裡的 this 指的是 call 的調用者,此處函數調用不指定的話即指向 window,指定讓其指向新創建的對象 obj,隻需要讓其第一個參數為 obj 對象即可,所以結果應該是第一個為 window,第二個為 obj 對象
<script> function eat(){ console.log(this); } var obj={ 'name':'小明', 'age':18 } eat.call() eat.call(obj) </script>
二:利用構造函數繼承父屬性
我們已經知道組合繼承是由構造函數和原型對象一起來實現的,其中構造函數實現的是屬性的繼承,原型對象實現的是方法的繼承,這版塊就走進利用父構造函數完成屬性的繼承
2.1 實現過程
其實現非常容易,隻需要在子構造函數中,使用 call 調用父構造函數(將其當做普通函數調用),其中在 call 方法中更改父構造函數中的 this 指向,由於 call 方法是在子構造函數中調用的,所以此處當做參數的 this 代表父構造函數中的 this 指向子構造函數的實例化對象,並傳參進去,所以相當於給子構造函數的實例化對象添加瞭屬性並賦值
<script> //聲明父構造函數 function Father(uname,uage,utel,sex){ this.uname=uname; this.uage=uage; this.utel=utel; this.sex=sex; } //聲明子構造函數,但是想繼承父類的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 實現過程分析
- 首先在子構造函數中使用 call 調用瞭父構造函數,並傳參給 call 的參數,其中第一個參數為 this 指向的改變,其餘為帶入的屬性值參數
- 我們知道構造函數中的 this 指向其實例化對象,所以本身父構造函數的 this 應該指向父構造函數的實例化對象,而此處 call 方法調用在子構造函數中,所以參數的指向更改為指向子構造函數的實例化對象
- 此處子構造函數的實例化對象就是 son1,所以父構造函數中的 this 指向的均是 son1,
- 所以就給 son1 添加並賦值瞭 uname,uage 等等屬性
三:利用原型對象繼承父方法
組合繼承的最後一版塊,利用原型對象來繼承方法,此處我們說明的是存放在構造函數的原型對象裡的公共方法的繼承
3.1 繼承父方法的錯誤演示
錯誤的繼承就是直接將父親的原型對象賦值給子的原型對象,這樣確實也可行,但是如果給子原型對象添加子類特有的方法,那父原型對象也會加上這個方法
<script> //聲明父構造函數 function Father(uname,uage){ this.uname=uname; this.uage=uage; } Father.prototype.money=function(){ console.log('我有很多錢'); } //聲明子構造函數 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>
我們可以發現父子的原型對象中確實都有瞭這個方法,證明確實這個辦法是行得通的
但是其也有問題存在,當我們想給子原型對象單獨添加其特有的方法時,就會出問題
上述問題給子原型對象添加特有方法的錯誤示例:
<script> //聲明父構造函數 function Father(uname,uage){ this.uname=uname; this.uage=uage; } Father.prototype.money=function(){ console.log('我有很多錢'); } //聲明子構造函數 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>
我們發現,我們確實給兒子添加上瞭兒子特有的方法,但是,父親的原型對象內也加上瞭這個方法,這並不滿足我們的預期,原因分析如下
問題原因
問題就在於我們的原型對象也是對象,對象是引用數據類型,引用數據類型的對象本質是在堆內存存放,是不能直接訪問的,其訪問是通過棧內存上的引用地址來找到去訪問,而我們此處采用的等號賦值的方式,實際上是將其在棧內存上的引用地址拷貝過去瞭,二者指向瞭同一塊內存空間,所以更改子原型對象,父原型對象也改變瞭
3.2 繼承父方法的正確做法
正確的做法是讓其子原型對象對象等於父實例化對象 Son.prototype=new Father(),其實我感覺有種高內聚低耦合的韻味,減少瞭直接聯系從而解決問題
<script> //聲明父構造函數 function Father(uname,uage){ this.uname=uname; this.uage=uage; } Father.prototype.money=function(){ console.log('我有很多錢'); } //聲明子構造函數 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() 這種方法繼承,看似已經天衣無縫,其實我們早就說過,采用等號賦值的方法會造成原型對象被覆蓋,裡面的構造函數 constructor 會被覆蓋掉,需要我們手動返回,所以七千萬要記得手動返回 constructor
<script> //聲明父構造函數 function Father(uname,uage){ this.uname=uname; this.uage=uage; } Father.prototype.money=function(){ console.log('我有很多錢'); } //聲明子構造函數 Son.prototype=new Father(); Son.prototype.constructor=Son; //手動返回構造函數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 _上的引用屬性任然是共享;
所以就有瞭我們看到的一句勸告:盡量把屬性定義在構造函數內,為瞭方便繼承吧;
還有就是實例一個對象,執行瞭兩次Super()
總結
到此這篇關於JavaScript進階教程之非extends的組合繼承的文章就介紹到這瞭,更多相關js非extends的組合繼承內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- JavaScript基礎之函數詳解
- JavaScript構造函數與原型之間的聯系
- 如何利用JavaScript 實現繼承
- javascript中的類,繼承,構造函數詳解
- JS中的六種繼承方式以及優缺點總結