javascript面向對象之繼承(上)

我們之前介紹了javascript面向對象的封裝的相關內容,還介紹了js的call方法,今天開始討論js的繼承
這篇文章參考了《javascript高級程序設計》(第三版),但內容不侷限於,網上很多關於js繼承的相關內容都是來自於這本書,有興趣的同學可以翻閱查看

原型鏈繼承

我們先通過一個栗子,瞭解一下原型鏈繼承。留意代碼註釋內容

//創建自定義構造函數
function Hqg() {
    this.name = '洪七公';
}
//在當前構造函數的原型鏈上添加屬性skill
Hqg.prototype.skill = '打狗棒'

//通過自定義構造函數Hqg實例化一個對象gj
const gj = new Hqg()
console.log(gj.skill);//=>打狗棒
//通過自定義構造函數Hqg實例化一個對象hr
const hr = new Hqg()
console.log(hr.skill);//=>打狗棒

一個簡單的栗子,郭靖和黃蓉都從洪七公那裏繼承到了skill 打狗棒,貌似沒什麼問題,我們繼續看demo

//上面代碼省略
const gj = new Hqg()//通過自定義構造函數Hqg實例化一個對象gj
gj.skill = "降龍十八掌"//重新對gj的skill賦值
console.log(gj.skill);//=>降龍十八掌
//通過自定義構造函數Hqg實例化一個對象hr
const hr = new Hqg()
console.log(hr.skill);//=>打狗棒

對郭靖的skill重新賦值,也沒有影響黃蓉的skill,我們打印一下gj

gj的skill屏蔽掉了原型鏈上的skill,所以gj的skill是降龍十八掌,而hr的skill依然是打狗棒

問題即將暴露

function Hqg() {
    this.name = '洪七公';
}
Hqg.prototype.skill = ['打狗棒']

const gj = new Hqg()
gj.skill.push ("降龍十八掌")//找到了原型鏈中的skill,並對其執行push操作,從而改變了構造函數中的skill屬性
console.log(gj.skill); //=>["打狗棒", "降龍十八掌"]
const hr = new Hqg()
//構造函數中的skill已經被改變
console.log(hr.skill); //=>["打狗棒", "降龍十八掌"]

總結一下,gj和hr都是Hqg的實例,繼承Hqg的屬性和方法,當Hqg的屬性或者方被改變了,後面的實例也會受影響,有時候這並不是我們希望的結果

借用構造函數

借用?就是使用call或者apply改變一下this指向,
就是子類的構造函數內部通過call或者apply調用父類的構造函數,如果對call方法有不瞭解的地方,可以翻看昨天的文章
舉一個栗子

//創建一個構造函數,並添加一些屬性
function Hqg() {
    this.name = '洪七公';
    this.job = '幫主';
    this.skill = ['降龍十八掌', '打狗棒']
}
//創建一個構造函數,並借用了Hqg的構造函數
function Hr() {
        Hqg.call(this)
        this.name = '黃蓉';
        this.job = ['相夫', '教子']
}
//創建一個構造函數,並借用了Hqg的構造函數
function Gj() {
    Hqg.call(this)
    this.name = '郭靖';
    this.job = ['吃飯', '睡覺']

}
const hr = new Hr();
console.log(hr);

const gj = new Gj();
console.log(gj);

輸出

這樣就避免了原型鏈繼承中,構造函數中的屬性或者方法被其他實例所改變的問題
⚠️:這裏要注意call方法的執行順序:

//部分代碼省略
function Hr() {
    this.name = '黃蓉';
    this.job = ['相夫', '教子']
    Hqg.call(this)
}
function Gj() {
    this.name = '郭靖';
    this.job = ['吃飯', '睡覺']
    Hqg.call(this)
}
//部分代碼省略

如果call在之後執行就會導致一個問題

值會被覆蓋,這個要注意!

借用構造函數進行傳參

這個算是一個升級的玩法吧

function Hqg(name,job,skill) {
  this.name = name;
  this.job = job;
  this.skill = skill
}
function Hr() {
  Hqg.call(this,'黃蓉',['相夫', '教子'],['打狗棒'])
}

function Gj() {
  Hqg.call(this,'郭靖',['吃飯', '睡覺'],['降龍十八掌'])
}
const hr = new Hr();
console.log(hr);

const gj = new Gj();
console.log(gj);

輸出

組合繼承

將原型鏈和借用構造函數技術組合到一起。
使用原型鏈實現對原型屬性和方法的繼承,用借用構造函數模式實現對實例屬性的繼承。
這樣既通過在原型上定義方法實現了函數複用,又能保證每個實例都有自己的屬性
一個栗子

function Hqg(name) {
  this.name = name
  this.skill = ["降龍十八掌","打狗棒"]
}
Hqg.prototype.sayName = function () {
  console.log(this.name);
}
function Hero(name, job) {
  Hqg.call(this, name);
  this.job = job
}
Hero.prototype = new Hqg();
Hero.prototype.constructor = Hero;
Hero.prototype.sayJob = function () {
  console.log(this.job)
}
var gj = new Hero('郭靖', '吃飯睡覺');
gj.skill.push("九陰真經");
console.log(gj);
var hr = new Hero('黃蓉', '相夫教子');
console.log(hr);

先看下輸出

我們把這個組合繼承和之前的兩個原型鏈繼承和借用構造函數繼承進行比較
不難發現組合繼承融合了他們的優點,成爲javascript中最常用的繼承模式
今天就討論前三個,還有三個明天繼續,不見不散


參考鏈接
你們真的瞭解JS的繼承嘛?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章