天天看點

繼承

類式繼承

// 聲明父類
function SuperClass() {
    this.superValue = true
}
// 為父類添加共有方法
SuperClass.prototype.getSuperValue = function () {
    return this.superValue
}
// 聲明子類
function SubClass() {
    this.subValue = false
}      

繼承的是父類的執行個體對象

// 繼承父類
SubClass.prototype = new SuperClass()
// 為子類添加共有方法
SubClass.prototype.getSubValue = function () {
    return this.subValue
}      

執行個體化子類看看結果

const sub = new SubClass()
sub.getSuperValue()    // true
sub.getSubValue()      // false      

通過instanceOf測試一下

// instanceof通過判斷原型鍊來确定對象是否是某個類的執行個體,而不關心對象與類自身的結構
sub instanceof SuperClass   // true
sub instanceof SubClass   // true
// 注意,它并不表示繼承,而是判斷前面是否是後面的執行個體
SubClass instanceof SuperClass   // false
SubClass.prototype instanceof SuperClass   // true      

 看起來挺不錯,好像繼承就應該是這樣,子類可以使用父類的屬性和方法,那麼這樣做是否有什麼缺點呢?我們再測試一下

function SuperClass() {
    this.books=["1","2","3"]
}
function SubClass() {}
SubClass.prototype= new SuperClass()
const sub1 = new SubClass()
const sub2 = new SubClass()
sub2.books     // [ '1', '2', '3' ]
sub1.books.push("4")
sub2.books     // [ '1', '2', '3', '4' ]      

 出現問題了,我們改動了一個執行個體sub1中的數組,但sub2執行個體中的數組也發生了變化,為什麼會這樣?

其實不難想到,出現這種情況的根本原因肯定是sub1直接改動了原型中的資料,因為sub1中沒有books屬性,就去自己的原型中找,在SuperClass中找到了,但是由于父類是個執行個體對象,是以其引用類型資料會被子類共用,導緻所有子類都出現了問題,這個是很不安全的,那麼如何避免這個問題呢?往下看

構造函數式繼承

通過使用call的方法,

// 聲明父類
function SuperClass(id) {
    this.books=["1","2","3"]
    this.id = id
}
// 父類聲明原型方法
SuperClass.prototype.showBooks = function () {
    console.log(this.books)
}
// 聲明子類
function SubClass(id) {
    SuperClass.call(this, id)
}      

 檢測引用類型,發現解決了問題

const sub_1 = new SubClass(12)
const sub_2 = new SubClass(20)
sub_1.books.push("4")
sub_1.books   // [ '1', '2', '3', '4' ]
sub_1.id      // 12
sub_2.books   // [ '1', '2', '3' ]
sub_2.id      // 20
sub_1.showBooks()   // sub_1.showBooks is not a function
      

但是又出現了新的問題,showBooks is not a function,不能查找到原型鍊中的showBooks方法

因為call相當于直接把父類構造函數中的屬性和方法複制了一份給子類,因為沒有涉及prototype,是以父類的原型子類無法繼承

組合式繼承

 那是不是說,把構造函數式繼承和類式繼承一起使用就可以避免這個問題了呢

function SuperClass(name) {
    this.name = name
    this.books = [ '1', '2', '3' ]
}
SuperClass.prototype.getName = function () {
    console.log(this.name)
}
function SubClass(name, time) {
    // 構造函數繼承
    SuperClass.call(this, name)
    this.time = time
}
// 類式繼承
SubClass.prototype = new SuperClass4()
SubClass.prototype.getTime = function () {
    console.log(this.time)
}      

測試結果發現,果然,問題都解決了

const sub_1 = new SubClass("vue", 2021)
sub_1.books.push("4")
console.log(sub_1.books)   // [ '1', '2', '3', '4' ]
sub_1.getName()            // vue
sub_1.getTime()            // 2021

const sub_2 = new SubClass("react", 1998)
console.log(sub_2.books)   // [ '1', '2', '3']
sub_2.getName()            // react
sub_2.getTime()            // 1998      

但是又出現了新的問題,因為call()在對象自身複制了一份原型共有屬性,但原型中也有一份父類共有屬性,父類的構造函數實際調用了兩遍

 寄生式組合繼承

建立一個過渡類,傳入父類的prototype,将父類掏空,同時繼承父類的原型鍊,傳回一個過渡類的執行個體對象

然後建立寄生組合式繼承方法,此處因為修改prototype後,子類 __proto__中的constructor會被覆寫丢失,是以重新修正一下其constructor

// 建立一個空的過渡類,減小構造函數的開銷
function inheritObject(o) {
    // 建立一個過渡類
    function F() {}
    F.prototype = o
    return new F()
}
// 寄生組合式繼承
function inheritPrototype(subClass, superClass) {
    // 複制一份父類的原型副本儲存在變量中
    const p = inheritObject(superClass.prototype)
    // 繼承
    subClass.prototype = p
    // 重新修正constructor
    p.constructor = subClass
}      

測試一下

// 定義父類
function SuperClass5(name) {
    this.name = name
    this.colors = ["red", "blue", "green"]
}
// 定義父類原型方法
SuperClass5.prototype.getName = function () {
    console.log(this.name)
}
// 定義子類
function SubClass5(name, time) {
    // 構造函數繼承
    SuperClass5.call(this, name)
    // 子類新增屬性
    this.time = time
}
// 寄生組合式繼承
inheritPrototype(SubClass5, SuperClass5)
SubClass5.prototype.getTime = function () {
    console.log(this.time)
}      

這回都沒有問題了

const test1 = new SubClass5("vue", 20)
const test2 = new SubClass5("react", 10)
test2.colors    // ['red', 'blue', 'green']
test1.colors.push("blue")
test2.colors    // ['red', 'blue', 'green']
test1.getName() // vue          

其實在ES5中提供了上述inheritObject建立過渡類的方法,即

Object.create(superClass.prototype)      

在ES6中更是提供了寄生組合式繼承的方法,即

Object.setPrototypeOf(SubClass.prototype, SuperClass.prototype)