類式繼承
// 聲明父類
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)