I'm writing some in-depth educational materials about the object model, constructor functions and class mechanisms in Javascript. As I was reading sources I found this statement that I took the time to understand its reasons and ultimately, I found it not to be entirely true.
The MDN article on new.target sugests using Reflect.construct in order to subclass constructor functions (of course, it also advocates for the use of class syntax in ES6 code, but my interest now is on constructor functions).
As I see it, the requirements for proper subclassing are:
- the base constructor must action on the constructed object first, then the derived constructor
- the constructed object's prototype must be Derived.prototype, whose prototype must beBase.prototype
- the static properties should be inherited: Derived's prototype should beBase.
There is also the implicit requirement that the base constructor's prechecks for new operator usage be satisfied. This is the key difference between constructor functions pre- and post-ES6:
in ES5, this precheck consisted of a this instanceof Base check (although ECMAScript's 5.1 specification only describes the behaviours of built-ins with and without the new operator, while being ambiguous about how this check is performed).
The mdn article says:
Note: In fact, due to the lack of
Reflect.construct(), it is not possible to properly subclass built-ins (likeErrorsubclassing) when transpiling to pre-ES6 code.
and gives this example of properly subclassing Map in ES6 code:
function BetterMap(entries) {
  // Call the base class constructor, but setting `new.target` to the subclass,
  // so that the instance created has the correct prototype chain.
  return Reflect.construct(Map, [entries], BetterMap);
}
BetterMap.prototype.upsert = function (key, actions) {
  if (this.has(key)) {
    this.set(key, actions.update(this.get(key)));
  } else {
    this.set(key, actions.insert());
  }
};
Object.setPrototypeOf(BetterMap.prototype, Map.prototype);
Object.setPrototypeOf(BetterMap, Map);
const map = new BetterMap([["a", 1]]);
map.upsert("a", {
  update: (value) => value + 1,
  insert: () => 1,
});
console.log(map.get("a")); // 2
However, if BetterMap were defined instead as
function BetterMap(entries) {
    var instance = new Map(entries);
    Object.setPrototypeOf(instance, BetterMap.prototype);
    return instance;
}
wouldn't we achieve the same result, both in ES5 and ES6 (after also replacing the arrow functions in the example above with function expressions)? This satisfies all the above requirements and complies with any prechecks that Map might do, as it construct the object with new.
I tested the code above with my suggested changes on Node downgraded to 5.12.0 and it worked as expected.
 
    