Adding a non-class-syntax method for subclassing native objects

# Renki Ivanko (9 years ago)

It's currently impossible to inherit the behavior of exotic builtin objects like Array without using the class extends syntax, which is surprising to someone who has only seen the class syntax described as syntactical sugar for prototypal inheritance, since it means that the class extends syntax can't be fully desugared to the non-class syntax. It means that describing the class syntax as syntactical sugar should be qualified to be fully accurate (since trying to desugar it with, for example, Babel can potentially break things even in browsers that otherwise support native subclassing), and that non-class syntax is a second-class citizen in the language. Adding a method for using the native subclassing without the class extends syntax would be consistent with ES5 adding Object.create() that allows using prototypal inheritance without the new operator.

The new method could be called Object.inherits(); a simplified example of how it could be used:

class Foo extends Array {}

// ...desugars to

function Foo() {
    function Foo(...args) {
        return Object.getPrototypeOf(Foo)(...args)
    }

    Object.inherits(Foo, Array)

    return Foo
}

Object.inherits = function(subClass, superClass) {
    subClass.prototype = Object.create(superClass.prototype, {
        constructor: {value: subClass},
        [Symbol.species]: {value: subClass},
    })
    Object.setPrototypeOf(subClass, superClass)
    // make subClass imbue the objects it constructs with native exotic
behavior here
}```
# Michael Theriot (9 years ago)

I believe you can do this with Reflect.construct.

function SubArray(arg1) {
  return Reflect.construct(Array, arguments, new.target);
}
SubArray.prototype = Object.create(Array.prototype);
Reflect.setPrototypeOf(SubArray, Array);

var arr = new SubArray();
arr instanceof SubArray; // true
arr instanceof Array; // true
arr[5] = 0; // should exotically update length property
arr.length === 6; // true

edit: fixed an issue mentioned below

# Allen Wirfs-Brock (9 years ago)

On May 3, 2016, at 8:01 PM, Michael Theriot <michael.lee.theriot at gmail.com> wrote:

I believe you can do this with Reflect.construct.

function SubArray(arg1) {
  return Reflect.construct(Array, arguments, SubArray);

this should probably be: return Reflect.construct(Array, arguments, new.target);

If you don’t pass new.target subclasses of SubArray (whether defined using a class declaration or procedurally) won’t be correctly constructed

# Renki Ivanko (9 years ago)

Thanks, I had missed the Reflect.construct() method. One problem with your solution is that the Array iterator methods like .map() use this.constructor[Symbol.species] to construct their result, so the prototype actually needs to be set like this:

SubArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: SubArray,
    writeable: true,
    enumerable: false,
    configurable: false,
  }
})

Setting the constructor property on the prototype is part of the MakeConstructor operation (for full parity the prototype property should be defined as non-enumerable, non-configurable as well).

# Renki Ivanko (9 years ago)

It would be helpful to have the MakeConstructor operation implemented as Reflect.makeConstructor(F, writablePrototype, prototype), since otherwise the alternative to the class extends syntax is very verbose. It would also be consistent with the Reflect.construct() method corresponding to the Construct abstract operation, and Object.create() mainly corresponding to ObjectCreate.