溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

好程序員分享JavaScript六種繼承方式詳解

發(fā)布時(shí)間:2020-08-08 23:27:19 來源:ITPUB博客 閱讀:128 作者:好程序員IT 欄目:web開發(fā)

好程序員 分享 JavaScript 六種繼承方式詳解 繼承是面向?qū)ο缶幊讨杏忠环浅V匾母拍睿? JavaScript 支持實(shí)現(xiàn)繼承,不支持接口繼承,實(shí)現(xiàn)繼承主要依靠原型鏈來實(shí)現(xiàn)的

 

原型鏈

 

首先得要明白什么是原型鏈,在一篇文章看懂 proto prototype 的關(guān)系及區(qū)別中講得非常詳細(xì)

 

原型鏈繼承基本思想就是讓一個(gè)原型對象指向另一個(gè)類型的實(shí)例

 

function SuperType() {

    this.property = true

}

 

SuperType.prototype.getSuperValue = function() {

    return this.property

}

 

function SubType() {

    this.subproperty = false

}

 

SubType.prototype = new SuperType()

 

SubType.prototype.getSubValue = function() {

    return this.subproperty

}

 

var instance = new SubType() console.log(instance.getSuperValue()) // true

代碼定義了兩個(gè)類型 SuperType SubType ,每個(gè)類型分別有一個(gè)屬性和一個(gè)方法, SubType 繼承了 SuperType ,而繼承是通過創(chuàng)建 SuperType 的實(shí)例,并將該實(shí)例賦給 SubType.prototype 實(shí)現(xiàn)的

 

實(shí)現(xiàn)的本質(zhì)是重寫原型對象 , 代之以一個(gè)新類型的實(shí)例,那么存在 SuperType 的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于 SubType.prototype 中了

 

我們知道,在創(chuàng)建一個(gè)實(shí)例的時(shí)候,實(shí)例對象中會有一個(gè)內(nèi)部指針指向創(chuàng)建它的原型,進(jìn)行關(guān)聯(lián)起來,在這里代碼 SubType.prototype = new SuperType() ,也會在 SubType.prototype 創(chuàng)建一個(gè)內(nèi)部指針,將 SubType.prototype SuperType 關(guān)聯(lián)起來

 

所以 instance 指向 SubType 的原型, SubType 的原型又指向 SuperType 的原型,繼而在 instance 在調(diào)用 getSuperValue() 方法的時(shí)候,會順著這條鏈一直往上找

 

添加方法

 

在給 SubType 原型添加方法的時(shí)候,如果,父類上也有同樣的名字, SubType 將會覆蓋這個(gè)方法,達(dá)到重新的目的。 但是這個(gè)方法依然存在于父類中

 

記住不能以字面量的形式添加,因?yàn)椋厦嬲f過通過實(shí)例繼承本質(zhì)上就是重寫,再使用字面量形式,又是一次重寫了,但這次重寫沒有跟父類有任何關(guān)聯(lián),所以就會導(dǎo)致原型鏈截?cái)?

 

function SuperType() {

    this.property = true

}

 

SuperType.prototype.getSuperValue = function() {

    return this.property

}

 

function SubType() {

    this.subproperty = false

}

 

SubType.prototype = new SuperType()

 

SubType.prototype = {

    getSubValue: function() {

        return this.subproperty

    }

}

 

var instance = new SubType() console.log(instance.getSuperValue()) // error

問題

 

單純的使用原型鏈繼承,主要問題來自包含引用類型值的原型。

 

function SuperType() {

    this.colors = ['red', 'blue', 'green']

}

 

function SubType() {}

 

SubType.prototype = new SuperType()

 

var instance1 = new SubType() var instance2 = new SubType()

 

instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"]

console.log(instance2.colors) // ["red", "blue", "green", "black"]

SuperType 構(gòu)造函數(shù)定義了一個(gè) colors 屬性,當(dāng) SubType 通過原型鏈繼承后,這個(gè)屬性就會出現(xiàn) SubType.prototype 中,就跟專門創(chuàng)建了 SubType.prototype.colors 一樣,所以會導(dǎo)致 SubType 的所有實(shí)例都會共享這個(gè)屬性,所以 instance1 修改 colors 這個(gè)引用類型值,也會反映到 instance2

 

借用構(gòu)造函數(shù)

 

此方法為了解決原型中包含引用類型值所帶來的問題

 

這種方法的思想就是在子類構(gòu)造函數(shù)的內(nèi)部調(diào)用父類構(gòu)造函數(shù),可以借助 apply() call() 方法來改變對象的執(zhí)行上下文

 

function SuperType() {

    this.colors = ['red', 'blue', 'green']

}

 

function SubType() {

    // 繼承 SuperType

    SuperType.call(this)

}

 

var instance1 = new SubType() var instance2 = new SubType()

 

instance1.colors.push('black') console.log(instance1.colors) // ["red", "blue", "green", "black"]

console.log(instance2.colors) // ["red", "blue", "green"]

在新建 SubType 實(shí)例是調(diào)用了 SuperType 構(gòu)造函數(shù),這樣以來,就會在新 SubType 對象上執(zhí)行 SuperType 函數(shù)中定義的所有對象初始化代碼

 

結(jié)果, SubType 的每個(gè)實(shí)例就會具有自己的 colors 屬性的副本了

 

傳遞參數(shù)

 

借助構(gòu)造函數(shù)還有一個(gè)優(yōu)勢就是可以傳遞參數(shù)

 

function SuperType(name) {

    this.name = name

}

 

function SubType() {

    // 繼承 SuperType

    SuperType.call(this, 'Jiang')

 

    this.job = 'student'

}

 

var instance = new SubType() console.log(instance.name) // Jiang

console.log(instance.job) // student

問題

 

如果僅僅借助構(gòu)造函數(shù),方法都在構(gòu)造函數(shù)中定義,因此函數(shù)無法達(dá)到復(fù)用

 

組合繼承 ( 原型鏈 + 構(gòu)造函數(shù) )

 

組合繼承是將原型鏈繼承和構(gòu)造函數(shù)結(jié)合起來,從而發(fā)揮二者之長的一種模式

 

思路就是使用原型鏈實(shí)現(xiàn)對原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對實(shí)例屬性的繼承

 

這樣,既通過在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能夠保證每個(gè)實(shí)例都有它自己的屬性

 

function SuperType(name) {

    this.name = name this.colors = ['red', 'blue', 'green']

}

 

SuperType.prototype.sayName = function() {

    console.log(this.name)

}

 

function SubType(name, job) {

    // 繼承屬性

    SuperType.call(this, name)

 

    this.job = job

}

 

// 繼承方法

SubType.prototype = new SuperType() SubType.prototype.constructor = SuperType SubType.prototype.sayJob = function() {

    console.log(this.job)

}

 

var instance1 = new SubType('Jiang', 'student') instance1.colors.push('black') console.log(instance1.colors) //["red", "blue", "green", "black"]

instance1.sayName() // 'Jiang'

instance1.sayJob() // 'student'

var instance2 = new SubType('J', 'doctor') console.log(instance2.colors) // //["red", "blue", "green"]

instance2.sayName() // 'J'

instance2.sayJob() // 'doctor'

這種模式避免了原型鏈和構(gòu)造函數(shù)繼承的缺陷,融合了他們的優(yōu)點(diǎn),是最常用的一種繼承模式

 

原型式繼承

 

借助原型可以基于已有的對象創(chuàng)建新對象,同時(shí)還不必因此創(chuàng)建自定義類型

 

function object(o) {

function F() {}

F.prototype = o

return new F()

}

object 函數(shù)內(nèi)部,先創(chuàng)建一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回這個(gè)臨時(shí)類型的一個(gè)新實(shí)例

 

本質(zhì)上來說, object 對傳入其中的對象執(zhí)行了一次淺復(fù)制

 

var person = {

    name: 'Jiang',

    friends: ['Shelby', 'Court']

}

 

var anotherPerson = object(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']

這種模式要去你必須有一個(gè)對象作為另一個(gè)對象的基礎(chǔ)

 

在這個(gè)例子中, person 作為另一個(gè)對象的基礎(chǔ),把 person 傳入 object 中,該函數(shù)就會返回一個(gè)新的對象

 

這個(gè)新對象將 person 作為原型,所以它的原型中就包含一個(gè)基本類型和一個(gè)引用類型

 

所以意味著如果還有另外一個(gè)對象關(guān)聯(lián)了 person , anotherPerson 修改數(shù)組 friends 的時(shí)候,也會體現(xiàn)在這個(gè)對象中

 

Object.create() 方法

 

ES5 通過 Object.create() 方法規(guī)范了原型式繼承,可以接受兩個(gè)參數(shù),一個(gè)是用作新對象原型的對象和一個(gè)可選的為新對象定義額外屬性的對象,行為相同,基本用法和上面的 object 一樣,除了 object 不能接受第二個(gè)參數(shù)以外

 

var person = {

    name: 'Jiang',

    friends: ['Shelby', 'Court']

}

var anotherPerson = Object.create(person) console.log(anotherPerson.friends)

寄生式繼承

 

寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù)

 

function createAnother(o) {

    var clone = Object.create(o) // 創(chuàng)建一個(gè)新對象

    clone.sayHi = function() { // 添加方法

        console.log('hi')

    }

    return clone // 返回這個(gè)對象

}

 

var person = {

    name: 'Jiang'

}

 

var anotherPeson = createAnother(person) anotherPeson.sayHi()

基于 person 返回了一個(gè)新對象 anotherPeson ,新對象不僅擁有了 person 的屬性和方法,還有自己的 sayHi 方法

 

在主要考慮對象而不是自定義類型和構(gòu)造函數(shù)的情況下,這是一個(gè)有用的模式

 

寄生組合式繼承

 

在前面說的組合模式 ( 原型鏈 + 構(gòu)造函數(shù) ) 中,繼承的時(shí)候需要調(diào)用兩次父類構(gòu)造函數(shù)

 

父類

 

function SuperType(name) {

this.name = name

this.colors = ['red', 'blue', 'green']

}

第一次在子類構(gòu)造函數(shù)中

 

function SubType(name, job) {

// 繼承屬性

SuperType.call(this, name)

 

this.job = job

}

第二次將子類的原型指向父類的實(shí)例

 

// 繼承方法

SubType.prototype = new SuperType()

當(dāng)使用 var instance = new SubType() 的時(shí)候,會產(chǎn)生兩組 name color 屬性,一組在 SubType 實(shí)例上,一組在 SubType 原型上,只不過實(shí)例上的屏蔽了原型上的

 

使用寄生式組合模式,可以規(guī)避這個(gè)問題

 

這種模式通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法

 

基本思路:不必為了指定子類型的原型而調(diào)用父類的構(gòu)造函數(shù),我們需要的無非就是父類原型的一個(gè)副本

 

本質(zhì)上就是使用寄生式繼承來繼承父類的原型,在將結(jié)果指定給子類型的原型

 

function inheritPrototype(subType, superType) {

    var prototype = Object.create(superType.prototype);

    prototype.constructor = subType;

    subType.prototype = prototype;

}

該函數(shù)實(shí)現(xiàn)了寄生組合繼承的最簡單形式

 

這個(gè)函數(shù)接受兩個(gè)參數(shù),一個(gè)子類,一個(gè)父類

 

第一步創(chuàng)建父類原型的副本,第二步將創(chuàng)建的副本添加 constructor 屬性,第三部將子類的原型指向這個(gè)副本

 

function SuperType(name) {

    this.name = name this.colors = ['red', 'blue', 'green']

}

 

SuperType.prototype.sayName = function() {

    console.log(this.name)

}

 

function SubType(name, job) {

    // 繼承屬性

    SuperType.call(this, name)

 

    this.job = job

}

 

// 繼承

inheritPrototype(SubType, SuperType)

 

var instance = new SubType('Jiang', 'student') instance.sayName()

> 補(bǔ)充:直接使用 Object.create 來實(shí)現(xiàn),其實(shí)就是將上面封裝的函數(shù)拆開,這樣演示可以更容易理解

 

function SuperType(name) {

    this.name = name this.colors = ['red', 'blue', 'green']

}

 

SuperType.prototype.sayName = function() {

    console.log(this.name)

}

 

function SubType(name, job) {

    // 繼承屬性

    SuperType.call(this, name)

 

    this.job = job

}

 

// 繼承

SubType.prototype = Object.create(SuperType.prototype)

 

// 修復(fù) constructor

SubType.prototype.constructor = SubType

 

var instance = new SubType('Jiang', 'student') instance.sayName()

ES6 新增了一個(gè)方法, Object.setPrototypeOf ,可以直接創(chuàng)建關(guān)聯(lián),而且不用手動(dòng)添加 constructor 屬性


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI