类式继承
// 声明父类
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)