ES2015 added the @@species feature, where subclassing of builtins is enhanced by creating instances not of this.constructor
but of this.constructor[Symbol.species]
. There are a few issues with @@species:
- @@species is called in some places but not others in a way that feels a bit inconsistent (from instance methods but not static methods). See bugs 1 2 (Kevin Smith)
- @@species feels a bit unnecessary. It was motivated initially by ensuring Zepto's strange use of proto would continue to work. There's already an extension point by overwriting the
constructor
field, a simpler fix would have been possible, and Zepto was updated a year ago to not use these patterns. - @@species will be a lot of work to implement across standard libraries of all JS engines without a performance regression.
This document explores whether we can remove the @@species feature and just build instances from new this.constructor()
instead.
@@species came up on TC39's agenda in an attempt to preserve compatibility with Zepto. A patch was merged on October 21, 2014 to remove the problematic use of proto, but the old code can be found here. Here's the relevant snippet:
zepto.Z = function(dom, selector) {
dom = dom || []
dom.__proto__ = $.fn
dom.selector = selector || ''
return dom
}
In this old version, $.fn
is an object literal with no constructor
property. Using @@species, callers to Zepto could make a quick fix by setting Object[Symbol.species] = Array
so that the Array methods that are copied onto $.fn continue to work (NB: or does it not need any intervention?).
However, a simpler fix is possible: ArraySpeciesCreate could check for a constructor which is Object and fall back to ArrayCreate(0) in that case. The spec text would be, in ArraySpeciesCreate, 5.c:
5.c. If Type(C) is Object
i. If Samevalue(C, %Object%), let C be undefined
Basically all other uses of @@species would just be removed, using the constructor as the species.
@@species adds extension points all over the place. Many callsites which had a simpler way of finding the constructor (either this.constructor
or a fixed constructor) now have an extra Get in the path, which could invoke arbitrary code. In some implementations, extra checks will have to be done at runtime to be able to run fastpaths where @@species remains the default. The standard library has to be updated in many different places.