Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use Symbol.species for plain objects?

The MDN gives the following working example of Symbol.species:

class MyArray extends Array {
  // Overwrite species to the parent Array constructor
  static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

The ECMAScript 2015 specifications says:

A function valued property that is the constructor function that is used to create derived objects.

The way I understand it it would be mainly used to Duck-Type in some way custom objects in order to trick the instanceof operator.
It seems very useful but I have not managed to use it on plain objects (FF 44.0a2):

let obj = {
  get [Symbol.species]() {
    return Array
  }
}

console.log(obj instanceof Array)  //false =(
console.log(obj instanceof Object) //true

Is there any way to use Symbol.species on plain objects in order to trick the instanceof operator?

like image 929
Kyll Avatar asked Nov 28 '15 14:11

Kyll


2 Answers

The way I understand it it would be mainly used to Duck-Type in some way custom objects in order to trick the instanceof operator.

Nope, that's what Symbol.hasInstance is good for (though it makes tricky constructors, e.g. for mixins, not tricky instances).

The point of Symbol.species is to let built-in methods determine the proper type for derived objects. Whenever a function is supposed to return a new instance of the same kind, it usually instantiates a new this.constructor, which might be a subclass of the class that defined the method. But you might not always want this when subclassing, and that's where Symbol.species comes in.
The example given by MDN is quite good, you just must not miss the distinction between a and mapped:

var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

Object.getPrototypeOf(a) == MyArray.prototype; // true
Object.getPrototypeOf(mapped) == Array.prototype; // true

(As you know, instanceof is was just sugar for the reverse of isPrototypeOf)

So what happens here is that the Array map method is called on an instance of MyArray, and creates a derived object that is now an instance of Array - because a.constructor[Symbol.species] said so. Without it, map would have created another MyArray.

This trick would work with plain objects just as well:

var b = {
    length: 0,
    map: Array.prototype.map,
    constructor: { // yes, usually a function
        [Symbol.species]: MyArray
    }
};
var mapped = b.map(x => x*x);
console.log(mapped instanceof MyArray); // true

I can't say whether it's any useful in this example, though :-)

like image 106
Bergi Avatar answered Oct 29 '22 00:10

Bergi


Good question. I admit that I'm not 100% sure, but I've gotten the impression that the answer is no.

I used two methods to determine this answer:

  1. Finding an environment that clearly supports all of the features necessary to test this, and running the code in that environment directly
  2. Referencing the spec

The first point wasn't easy to test, and I wasn't entirely sure that I successfully did it. You used Firefox 44, but I decided to test another environment due to the fact that Kangax's support table says it only supports the existence of Symbol.species, and none of the Array-like features. To see this, you can expand the Symbol.species section in the link above.

In this case, even Kangax is a bit confusing. I would expect there to be non-Array specific tests for Symbol.species aside from the existence test, but maybe I'm just not knowledge enough to understand that those tests cover all of the functionality of Symbols. I'm not sure!

While browsing the table, I saw that Edge supports all of the Symbol.species features. I loaded up my Windows VM, hopped on over to Edge 20, and pasted your code.

In Edge 20, the code had the same result as your test in Firefox 44.

So this began to give me confidence that the feature doesn't work in the way that you want it to. Just to make sure that Edge had its JavaScript in order, I decided to run the code from the MDN article you linked, and...I got a syntax error! It appears Edge is still updating its Class implementation due to late-in-the-game changes to the Class spec, so it's not enabled.

Their current implementation is, however, available through an experimental flag. So I flipped that on and was able to reproduce the result that MDN got.

And that was the extent of my JavaScript environment testing. I don't have 100% confidence in this test, but I would say that I'm fairly confidence in the result.

So onto the second method I used to determine my result: the specification.

I admit that I'm no expert at the ECMAScript specification. I do my best to understand it, but it is likely that I might misinterpret some of the information in there. The reason for this is because I'm not an ECMAScript master, so some of the technical jargon used is just way over my head.

Nevertheless, I can sometimes get the gist of what is being said. Here's a snippet that's about the Symbol @@species:

Methods that create derived collection objects should call @@species to determine the constructor to use to create the derived objects. Subclass constructor may over-ride @@species to change the default constructor assignment.

The phrase "derived objects" is used quite frequently in describing this symbol, but it's not defined anywhere. One might interpret it to be a synonym of sorts with instantiated. If that's a correct interpretation, then this can likely only be used with constructors or classes.

Either way, it sounds as if @@species is an active Symbol, rather than a passive one. By this I mean that it sounds like it's a function that's actually called during the creation of an Object, which is then what determines the instanceof value, rather than a function that's called at the time of instanceof being called.

But anyway, take that interpretation with a grain of salt. Once again, I'm not 100% certain of any of this. I considered posting this as a comment rather than an answer, but it ended up getting quite long.

like image 44
jamesplease Avatar answered Oct 29 '22 00:10

jamesplease