js六大继承方式 + es6继承

188 阅读4分钟

JavaScript继承

1.原型链

  • 核心: 将父类的实例赋值给子类的原型
  • 优点:方法复用
    • 由于方法定义在父类的原型上,复用了父类构造函数的方法
  • 缺点:
    • 所有子类实例共享了父类构造函数原型上的引用值
    • 子类不能给父类传递参数
function SuperType() {
    this.superProperty = true
}
SuperType.prototype.getSuperValue = function () {
    return this.superProperty
}
function SubType() {
    this.subProperty = false
}
// 核心
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
    return this.subProperty
}

let person = new SubType();
person.getSuperValue() // true
person.getSubValue() // false
console.log(person.superProperty) // true
console.log(person) // SubType {subProperty: false}

2.盗用构造函数

  • 核心: 在子类构造函数中调用父类构造函数,等于把父类的实例属性复制给子类
  • 优点:实例间属性独立
    • 子类实例可以向父类构造函数传递参数
    • 子类实例不共享父类构造函数的引用属性
  • 缺点:
    • 由于方法的父类构造函数中定义导致方法不能复用(因为每次创建子类实例的时都会创建一次父类的方法)
    • 子类无法继承父类原型上的属性(因为没有用到原型)
function SuperType(name) {
    this.name = name
    this.colors = ['red','blue','green']
}
function SubType(age) {
     // 继承 SuperType
    SuperType.call(this,'xiaoming')
    this.age = age
}
let person = new SubType(18)
console.log(person.age) // 18
person.colors.push('pink') // ['red','blue','green','pink']
let person1 = new SubType(19)
console.log(person1.age) // 19
console.log(person1.colors) // ['red','blue','green']

3.组合继承

  • 核心: 属性通过调用父类构造函数实现继承,方法通过将父类的实例对象赋值给子类的原型来实现复用
  • 优点:
    • 保留构造函数的优点:可以向父类构造函数传递参数,子类不共享父类的引用属性
    • 保留原型链的优点:父类构造函数原型的方法可以被子类复用
    • 组合继承保留了 instanceof 操作符 和 isPrototypeOf方法识别对象的能力
  • 缺点:
    • 调用了2次父类构造函数,会多存在一份多余的父类实例属性
function SuperType(name) {
    this.name = name
    this.colors = ['red','blue','green']
}
SuperType.prototype.getName = function() {
    console.log(this.name)
}
function SubType(name,age) {
    // 继承属性- 第二次调用
    SuperType.call(this,name)
    this.age = age
}
// 继承方法 第一次调用
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType // 修改构造函数指向
SubType.prototype.getAge = function() {
    console.log(this.age)
}
let instance1 = new SubType('xiaoming',18)
instance1.colors.push('pink')
console.log(instance1.colors) // ['red','blue','green','pink']
instance1.getName() // xiaoming
instance1.getAge() // 18

let instance2 = new SubType('xiaohong', 28)
console.log(instance2.colors) // ['red','blue','green']
instance2.getName() // xiaohong
instance2.getAge() // 28

4.原型式继承

  • 核心: 基于已有的对象创建对象,同时不必因此创建自定义类型
  • 优点:
    • 不需要创建构造函数,想让对象直接属性共享的场合
  • 缺点:
    • 和原型链一样属性和方法会在各个子类中共享
// 临时创建一个构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时构造函数的实例
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
let person = {
    name: 'xiaoming',
    colors: ['red','blue','green']
}
let anotherPerson = Object.create(person)
anotherPerson.name = 'xiaohong'
anotherPerson.colors.push('black')

let anotherPerson1 = Object.create(person)
anotherPerson1.name = 'jack'
anotherPerson1.colors.push('pink')
console.log(person.colors) // ['red','blue','green','black','pink']

5. 寄生式继承

  • 寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
  • 缺点: 和构造函数模式一样,函数无法重用
  • 使用场景:寄生式继承适合主要关注对象,而不在乎类型和构造函数的场景
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function createAnother(original) {
    let clone = object(original) // 通过调用函数创建一个新对象
    clone.sayHi = function() {  // 以某种方式增强这个对象
        console.log('hi')
    }
    return clone  // 返回这个对象
}
// eg
let person = {
    name: 'xiaoming',
    friends: ['Jack','Rose','Shelby']
}

let anotherPerson = createAnother(person)
anotherPerson.sayHi() // 'hi'

6.寄生式组合继承(引用类型的最佳继承模式)

  • 核心:在子类中调用父类构造函数实现属性继承,通过Object.create(Parent.prototype) 方法创建新对象实现对父类原型方法的继承
  • 优点: 完美
  • 缺点: 无
function Parent(name) {
    this.name = name // 实例基本属性 (该属性,强调私有,不共享)
    this.ar = [1] // 实例引用属性 (该属性,强调私有,不共享)
}
Parent.prototype.sayName = function () { // 方法需要复用,共享的方法定义在父类的原型上
    console.log(this.name)
}
function Child(name,like) {
    Parent.call(this,name) // 核心 继承属性
    this.like = like
}
Child.prototype = Object.create(Parent.prototype)
// 修复构造函数指向
Child.prototype.constructor = Child

let boy1 = new Child('xiaoming','apple')
let boy2 = new Child('xiaohong','orange')
let parent = new Parent('baba')
console.log(boy1.constructor) // Child
console.log(boy2.constructor) // child
console.log(parent.constructor) // Parent

7.ES6 继承

- 使用super的注意事项

  • super只能在派生类构造函数和静态方法中使用。
  • 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法。
  • 调用super()会调用父类构造函数,并将返回的实例赋值给this
  • super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
  • 如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数。
  • 在类构造函数中,不能在调用 super()之前引用 this
  • 如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象。
class Parent {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    sayName() {
        console.log(this.name)
    }
}
class Child extends Parent {
    constructor(name,age) {
        // 核心
        super(name,age)
        this.friends = ['Jack','Rose']
    }
    sayAge() {
        console.log(this.age)
    }
}
let person1 = new Child('小明',18)
let person2 = new Child('小红',19)
console.log(person1 instanceof Child) // true
console.log(person1 instanceof Parent) // true
person1.friends.push('小张')
console.log(person1.friends) // ['Jack','Rose','小张']
console.log(person2.friends) // ['Jack','Rose']
person1.sayName() // 小明
person2.sayName() // 小红

参考文章

js继承、构造函数继承、原型链继承、组合继承、组合继承优化、寄生组合继承