您好,登錄后才能下訂單哦!
好程序員 分享 JavaScript 六種繼承方式詳解 , 繼承是面向對象編程中又一非常重要的概念, JavaScript 支持實現繼承,不支持接口繼承,實現繼承主要依靠原型鏈來實現的
原型鏈
首先得要明白什么是原型鏈,在一篇文章看懂 proto 和 prototype 的關系及區別中講得非常詳細
原型鏈繼承基本思想就是讓一個原型對象指向另一個類型的實例
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
代碼定義了兩個類型 SuperType 和 SubType ,每個類型分別有一個屬性和一個方法, SubType 繼承了 SuperType ,而繼承是通過創建 SuperType 的實例,并將該實例賦給 SubType.prototype 實現的
實現的本質是重寫原型對象 , 代之以一個新類型的實例,那么存在 SuperType 的實例中的所有屬性和方法,現在也存在于 SubType.prototype 中了
我們知道,在創建一個實例的時候,實例對象中會有一個內部指針指向創建它的原型,進行關聯起來,在這里代碼 SubType.prototype = new SuperType() ,也會在 SubType.prototype 創建一個內部指針,將 SubType.prototype 與 SuperType 關聯起來
所以 instance 指向 SubType 的原型, SubType 的原型又指向 SuperType 的原型,繼而在 instance 在調用 getSuperValue() 方法的時候,會順著這條鏈一直往上找
添加方法
在給 SubType 原型添加方法的時候,如果,父類上也有同樣的名字, SubType 將會覆蓋這個方法,達到重新的目的。 但是這個方法依然存在于父類中
記住不能以字面量的形式添加,因為,上面說過通過實例繼承本質上就是重寫,再使用字面量形式,又是一次重寫了,但這次重寫沒有跟父類有任何關聯,所以就會導致原型鏈截斷
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 構造函數定義了一個 colors 屬性,當 SubType 通過原型鏈繼承后,這個屬性就會出現 SubType.prototype 中,就跟專門創建了 SubType.prototype.colors 一樣,所以會導致 SubType 的所有實例都會共享這個屬性,所以 instance1 修改 colors 這個引用類型值,也會反映到 instance2 中
借用構造函數
此方法為了解決原型中包含引用類型值所帶來的問題
這種方法的思想就是在子類構造函數的內部調用父類構造函數,可以借助 apply() 和 call() 方法來改變對象的執行上下文
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 實例是調用了 SuperType 構造函數,這樣以來,就會在新 SubType 對象上執行 SuperType 函數中定義的所有對象初始化代碼
結果, SubType 的每個實例就會具有自己的 colors 屬性的副本了
傳遞參數
借助構造函數還有一個優勢就是可以傳遞參數
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
問題
如果僅僅借助構造函數,方法都在構造函數中定義,因此函數無法達到復用
組合繼承 ( 原型鏈 + 構造函數 )
組合繼承是將原型鏈繼承和構造函數結合起來,從而發揮二者之長的一種模式
思路就是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承
這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性
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'
這種模式避免了原型鏈和構造函數繼承的缺陷,融合了他們的優點,是最常用的一種繼承模式
原型式繼承
借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型
function object(o) {
function F() {}
F.prototype = o
return new F()
}
在 object 函數內部,先創建一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回這個臨時類型的一個新實例
本質上來說, object 對傳入其中的對象執行了一次淺復制
var person = {
name: 'Jiang',
friends: ['Shelby', 'Court']
}
var anotherPerson = object(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
這種模式要去你必須有一個對象作為另一個對象的基礎
在這個例子中, person 作為另一個對象的基礎,把 person 傳入 object 中,該函數就會返回一個新的對象
這個新對象將 person 作為原型,所以它的原型中就包含一個基本類型和一個引用類型
所以意味著如果還有另外一個對象關聯了 person , anotherPerson 修改數組 friends 的時候,也會體現在這個對象中
Object.create() 方法
ES5 通過 Object.create() 方法規范了原型式繼承,可以接受兩個參數,一個是用作新對象原型的對象和一個可選的為新對象定義額外屬性的對象,行為相同,基本用法和上面的 object 一樣,除了 object 不能接受第二個參數以外
var person = {
name: 'Jiang',
friends: ['Shelby', 'Court']
}
var anotherPerson = Object.create(person) console.log(anotherPerson.friends)
寄生式繼承
寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數
function createAnother(o) {
var clone = Object.create(o) // 創建一個新對象
clone.sayHi = function() { // 添加方法
console.log('hi')
}
return clone // 返回這個對象
}
var person = {
name: 'Jiang'
}
var anotherPeson = createAnother(person) anotherPeson.sayHi()
基于 person 返回了一個新對象 anotherPeson ,新對象不僅擁有了 person 的屬性和方法,還有自己的 sayHi 方法
在主要考慮對象而不是自定義類型和構造函數的情況下,這是一個有用的模式
寄生組合式繼承
在前面說的組合模式 ( 原型鏈 + 構造函數 ) 中,繼承的時候需要調用兩次父類構造函數
父類
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
第一次在子類構造函數中
function SubType(name, job) {
// 繼承屬性
SuperType.call(this, name)
this.job = job
}
第二次將子類的原型指向父類的實例
// 繼承方法
SubType.prototype = new SuperType()
當使用 var instance = new SubType() 的時候,會產生兩組 name 和 color 屬性,一組在 SubType 實例上,一組在 SubType 原型上,只不過實例上的屏蔽了原型上的
使用寄生式組合模式,可以規避這個問題
這種模式通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法
基本思路:不必為了指定子類型的原型而調用父類的構造函數,我們需要的無非就是父類原型的一個副本
本質上就是使用寄生式繼承來繼承父類的原型,在將結果指定給子類型的原型
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
該函數實現了寄生組合繼承的最簡單形式
這個函數接受兩個參數,一個子類,一個父類
第一步創建父類原型的副本,第二步將創建的副本添加 constructor 屬性,第三部將子類的原型指向這個副本
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()
> 補充:直接使用 Object.create 來實現,其實就是將上面封裝的函數拆開,這樣演示可以更容易理解
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)
// 修復 constructor
SubType.prototype.constructor = SubType
var instance = new SubType('Jiang', 'student') instance.sayName()
ES6 新增了一個方法, Object.setPrototypeOf ,可以直接創建關聯,而且不用手動添加 constructor 屬性
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。