Yu Duan
Yu Duan

Reputation: 103

javaScript - multiple inheritance in ES6,

I'm trying to inherit class "EventEmitter" and a pre defined class "Person", here is the code

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age; 
    }
    introduces() {
        return `My name is ${this.name}. I am ${this.age} years old.`;
    }
}; 

\\here comes the mixin part

function mix(...mixins) {
    class Mix {}
    for (let mixin of mixins) {
        copyProperties(Mix, mixin);
        copyProperties(Mix.prototype, mixin.prototype);
    }
    return Mix;
}

function copyProperties(target, source) {
    for (let key of Reflect.ownKeys(source)) {  
        if (key !== "constructor" &&  key !== "prototype" && key !== "name") {
            let desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}

I intend to create a new class 'PersonWithEmitter', and still call the constructor like below:

class PersonWithEmitter extends mix(Person,EventEmitter){
    constructor(name,age){
        super(name,age)
        \\do something else
    }

Here comes the issue, when I create a new instance of 'PersonWithEmitter' like this let someOne = new PersonWithEmitter("Tom",21), will not get what I what, In the new class, I want to use this.name and this.age, which is still undefined. So how can I change my code, So the new class can both have its parent's methods and only class "Person"'s constructor? Pardon me for my broken English.

Upvotes: 2

Views: 3845

Answers (1)

Estus Flask
Estus Flask

Reputation: 223259

In many cases multiple inheritance in JavaScript indicates wrong design decision. It may result in hacky objects that don't behave as they should. The way it is done should always be determined by particular objects. In some cases a shallow copy of own properties is needed, in another the entire prototype chain should be traversed. Composition over inheritance is often a better choice.

The problem in the code above is that class constructors are not called. Mix has empty constructor. This is the reason why PersonWithEmitter doesn't work as expected.

Multiple constructor function calls can generally be stacked like:

function Foo(...args) {
  let _this = this;
  _this = Bar.apply(_this, args);
  _this = Baz.apply(_this, args);
  return _this;
}

This won't work if Bar or Baz is ES6 class because it contains a mechanism that prevents it from being called without new. In this case they should be instantiated:

function Foo(...args) {
  copyProperties(this, new Bar(...args));
  copyProperties(this, new Baz(...args));
}

Static and prototype properties may also be copied to Foo like is shown in code above.

If the case is narrowed down to Node.js EventEmitter, it can be handled like a special case. Its implementation is certain and stable. It is already known that EventEmitter does initialization in constructor, it has a shallow prototype chain and property descriptors. So it likely should be:

class Foo extends Bar {
  constructor(...args) {
    super(...args)
    EventEmitter.call(this);
    // or
    // EventEmitter.init.call(this);
}
copyProperties(Foo.prototype, EventEmitter.prototype);

Upvotes: 3

Related Questions