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