Built-in constructors such as Array, Error, Map, and Promise allocate their own special objects (an exotic array, an object with [[ErrorData]], and so on). Because new passes new.target through super(...), the built-in uses GetPrototypeFromConstructor(newTarget, ...) to give that object the subclass's prototype. So class Stack extends Array produces a real array (Array.isArray is true, length works) that inherits from Stack.prototype. ES5-style Array.call(this) can't do this: it creates and discards an exotic object, and this stays ordinary.
Symbol.species. map, filter, slice, splice, concat, flat, and flatMap build their results with ArraySpeciesCreate: read this.constructor, then its [Symbol.species] (a getter that returns the constructor itself by default), and call new Species(length). So:
- results are instances of your subclass, and your methods work on them;
- your constructor is called with a length, so a constructor with a different signature silently misbehaves (the
labelbelow becomes2); static get [Symbol.species]() { return Array; }makes those methods return plain arrays.Promise.prototype.then,ArrayBuffer,RegExpand typed arrays consult species too.MapandSetdefine the getter, but their methods don't use it.
Error. The most common legitimate subclass. Call super(message, options) (so cause works), set name yourself (it defaults to "Error"), and add fields. instanceof works.
Recommendation: subclass Error; subclass Array/Map/Promise only when instances must pass Array.isArray/be accepted as the built-in and your constructor accepts the built-in's arguments. Otherwise wrap the built-in in a class and expose only the methods you need. MDN notes that the standards committee considers the built-in subclassing mechanism over-engineered, with performance and security costs.