extending an ES6 class using ES5 syntax?

Rob Brackett rob at robbrackett.com
Mon May 16 01:23:32 UTC 2016


> I'm trying to provide a path where common code can migrate to ES6 classes before all the consumers have.   So I was really looking for something that didn't require the subclasses to be touched at all (I wasn't clear about this).

I hope it was clear that newless supports this (functions, classes, and other newless constructors can all inherit from newless constructors and vice versa). Obviously there is the caveat noted in the README about dealing with `this`. In newless, it’s mostly a non-issue unless the *super* class needs to keep a reference to the actual instance that was created.

You simply can’t get around the fact that calling an ES6 class constructor will create a new object instance, even if you already have one with the right prototype chain (Even when using `Reflect.construct()`, which you really can’t depend on in practice). Newless tries to work around this by setting the prototype of the instance you already had (i.e. the one a function constructor called `SuperConstructor.call(this)` with) to the object that was created when instantiating the underlying class constructor. This preserves all the instance properties, but means that the `this` in the superclass’s constructor is not the exact same object as the `this` in the subclass’s constructor. (It also *returns* the `this` from the superclass’s constructor, so a knowledgable inheriting function can work with that instead and be totally safe with no caveats.)

> I have a fair amount of control over how the inheritance is setup and how the ES6 class is written but that is about it.

However! If you are in control of the the class hierarchy from your class down to the root, you can do a little better than newless can if you are willing to get a little tricky:

```
class FunctionInheritable {
  constructor() {
    return this._constructor.apply(this, arguments);
  }
  _constructor() {}
  static call(context, ...args) {
    return this.apply(context, args);
  }
  static apply(context, args) {
    return this.prototype._constructor.apply(context, args) || context;
  }
}

class YourActualLibraryClass extends FunctionInheritable {
  // all your inheritable classes will have to use `_constructor` instead of `constructor`
  _constructor(firstArg) {
    // do whatever you want, there are no special requirements on what’s in here
    this.something = firstArg;
    global.libraryInstance = this;
  }
  someLibraryClassFunction() {
    return this.something;
  }
}

// any ES5 or earlier function-style “class” can now inherit with no changes
function SomeEs5FunctionConstructor() {
  this.somethingElse = 'whatever';
  YourActualLibraryClass.call(this, 'something');
}
SomeEs5FunctionConstructor.prototype = Object.create(YourActualLibraryClass.prototype);
```

But, bottom line, you’re going to have to do something funky if you want to upgrade function-style classes in the middle of an inheritance chain to ES6 classes without imposing any changes on the ultimate subclasses at the end of the chain. ES6 simply goes out of its way to make it impossible to do that without some hackery, be it some code like the above, a tool like newless, or a transpiler (that does not fully enforce ES6 restrictions).

As a *library* author, I’ve run into the same issues you have here—with rare exception, it’s still much too early to impose ES6-only support on a library’s users. It’s why libraries I work on generally haven’t moved to ES6 classes unless they’re doing something special as noted above.

Hope that helps,

Rob



More information about the es-discuss mailing list