继承

类式继承

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

 

上一篇:Exploiting DRAM Restore Time Variations in Deep Sub-micron Scaling


下一篇:【小技巧】如何使上线前初始化数据的SQL脚本可测试?