如何实现寄生组合式继承

2 阅读3分钟

寄生组合式继承是组合式继承的优化版本,解决了组合式继承中父类构造函数被调用两次的问题,是目前JavaScript中最理想的继承方式。

1. 核心实现原理

// 父类
function Parent(name) {
    this.name = name
    this.colors = ['red', 'blue', 'green']
}

Parent.prototype.sayName = function() {
    console.log('Parent name:', this.name)
}

// 子类
function Child(name, age) {
    // 1. 构造函数继承(只调用一次父类构造函数)
    Parent.call(this, name)  // 关键点:只在这里调用一次
    this.age = age
}

// 2. 原型继承(不使用 new Parent(),避免再次调用构造函数)
Child.prototype = Object.create(Parent.prototype)  // 关键点:创建原型副本
Child.prototype.constructor = Child  // 修复constructor指向

// 子类自己的方法
Child.prototype.sayAge = function() {
    console.log('Child age:', this.age)
}

// 测试
const child1 = new Child('小明', 10)
child1.colors.push('yellow')
console.log(child1.colors)  // ['red', 'blue', 'green', 'yellow']
child1.sayName()  // Parent name: 小明
child1.sayAge()   // Child age: 10

const child2 = new Child('小红', 8)
console.log(child2.colors)  // ['red', 'blue', 'green']

2. 封装成通用函数

/**
 * 实现寄生组合式继承
 * @param {Function} Child 子类构造函数
 * @param {Function} Parent 父类构造函数
 */
function inherit(Child, Parent) {
    // 创建父类原型的副本
    const prototype = Object.create(Parent.prototype)
    // 修复constructor指向
    prototype.constructor = Child
    // 设置子类的原型
    Child.prototype = prototype
}

// 使用示例
function Animal(name) {
    this.name = name
}

Animal.prototype.sayName = function() {
    console.log('I am ' + this.name)
}

function Dog(name, breed) {
    Animal.call(this, name)
    this.breed = breed
}

// 调用继承函数
inherit(Dog, Animal)

Dog.prototype.bark = function() {
    console.log(this.name + ' says: Woof!')
}

// 测试
const dog = new Dog('Buddy', 'Golden Retriever')
dog.sayName()  // I am Buddy
dog.bark()     // Buddy says: Woof!
console.log(dog instanceof Dog)    // true
console.log(dog instanceof Animal) // true

3. 更完善的继承函数(支持静态方法继承)

function extend(Child, Parent) {
    // 继承原型方法
    Child.prototype = Object.create(Parent.prototype)
    Child.prototype.constructor = Child
    
    // 继承静态方法(ES5方式)
    for (var key in Parent) {
        if (Parent.hasOwnProperty(key)) {
            Child[key] = Parent[key]
        }
    }
    
    // ES6+ 方式(如果支持)
    if (Object.setPrototypeOf) {
        Object.setPrototypeOf(Child, Parent)
    } else if (Child.__proto__) {
        Child.__proto__ = Parent
    }
    
    return Child
}

// 使用示例
function Shape(color) {
    this.color = color
}

// 实例方法
Shape.prototype.getColor = function() {
    return this.color
}

// 静态方法
Shape.create = function(color) {
    return new Shape(color)
}

function Circle(color, radius) {
    Shape.call(this, color)
    this.radius = radius
}

extend(Circle, Shape)

Circle.prototype.getArea = function() {
    return Math.PI * this.radius * this.radius
}

// 测试
const circle = new Circle('red', 10)
console.log(circle.getColor())  // red
console.log(circle.getArea())   // 314.159...

// 静态方法也被继承
console.log(Circle.create)  // [Function: create]

4. ES5兼容方案

// 兼容旧浏览器的Object.create
if (typeof Object.create !== 'function') {
    Object.create = function(proto) {
        function F() {}
        F.prototype = proto
        return new F()
    }
}

// 通用继承函数(兼容版本)
function inheritPrototype(Child, Parent) {
    // 创建原型副本
    var prototype = Object.create(Parent.prototype)
    prototype.constructor = Child
    Child.prototype = prototype
}

// 使用示例
function Vehicle(type) {
    this.type = type
}

Vehicle.prototype.move = function() {
    console.log(this.type + ' is moving')
}

function Car(type, brand) {
    Vehicle.call(this, type)
    this.brand = brand
}

inheritPrototype(Car, Vehicle)

Car.prototype.honk = function() {
    console.log(this.brand + ' honks!')
}

5. 与组合式继承的对比

// 组合式继承(有缺陷的版本)
function Parent(name) {
    this.name = name
    console.log('Parent constructor called')
}

Parent.prototype.sayName = function() {
    console.log(this.name)
}

function Child(name, age) {
    Parent.call(this, name)  // 第一次调用
    this.age = age
}

Child.prototype = new Parent()  // 第二次调用 ❌
Child.prototype.constructor = Child

// 每次创建Child实例都会打印两次"Parent constructor called"

// 寄生组合式继承(优化版本)
function OptimizedChild(name, age) {
    Parent.call(this, name)  // 只调用一次 ✅
    this.age = age
}

// 使用Object.create,不调用Parent构造函数
OptimizedChild.prototype = Object.create(Parent.prototype)
OptimizedChild.prototype.constructor = OptimizedChild

// 只打印一次"Parent constructor called"
`

## 6. 实际应用示例

```js
// 事件发射器基类
function EventEmitter() {
    this._events = {}
}

EventEmitter.prototype.on = function(event, listener) {
    if (!this._events[event]) {
        this._events[event] = []
    }
    this._events[event].push(listener)
}

EventEmitter.prototype.emit = function(event, ...args) {
    const listeners = this._events[event]
    if (listeners) {
        listeners.forEach(listener => listener.apply(this, args))
    }
}

// 自定义组件
function Component(name) {
    EventEmitter.call(this)
    this.name = name
    this.state = {}
}

// 寄生组合式继承
Component.prototype = Object.create(EventEmitter.prototype)
Component.prototype.constructor = Component

Component.prototype.setState = function(newState) {
    Object.assign(this.state, newState)
    this.emit('stateChanged', this.state)
}

// 使用
const comp = new Component('MyComponent')
comp.on('stateChanged', function(state) {
    console.log('State changed:', state)
})

comp.setState({ loading: true })  // 触发事件

优点总结

  1. 只调用一次父类构造函数:效率更高
  2. 原型链保持纯净:没有多余的父类实例属性
  3. instanceof正常工作:原型链完整
  4. 可向父类传参:通过构造函数继承实现
  5. 可扩展性强:易于添加额外功能

寄生组合式继承是目前JavaScript中最完美的继承实现方式,也是ES6的class extends语法在底层的实现原理。