I was reading over the draft for ES6, and I noticed this note in the Object.prototype.toString
section:
Historically, this function was occasionally used to access the string value of the [[Class]] internal property that was used in previous editions of this specification as a nominal type tag for various built-in objects. This definition of toString preserves the ability to use it as a reliable test for those specific kinds of built-in objects but it does not provide a reliable type testing mechanism for other kinds of built-in or program defined objects.
From reading this thread on es-discuss, it sounds like [[Class]]
is being replaced with [[NativeBrand]]
in the ES6 draft so that they can specify it as being non-extensible (those were at least Allen Wirfs-Brock's thoughts).
Curious, I ran a quick test in FireFox and Chrome (with experimental JavaScript enabled):
Object.prototype.toString.apply(new WeakMap());
=> '[object WeakMap]'
"WeakMap"
is not one of the [[NativeBrand]]
s specified in the ES6 draft. However, this test returned "[object WeakMap]"
on both browsers.
So I'm confused. I have a few questions.
1. Do Chrome and Firefox behave correctly?
From one way of reading the draft it sounds like they should return [object Object]
(and all of this is pretty new, so I wouldn't be surprised to see this change in future editions of these browsers). However, it's hard for me to understand the intention of this section of the draft, especially since there are some places with "???"
.
Does anyone who has been following es-discuss more fervently have any relevant information? Or anyone who can understand the draft language better?
2. Is there an alternative to Object.prototype.toString
?
From the note quoted above it makes it sound as if Object.prototype.toString
is retained for legacy reasons, as if there's something new now that should be used instead. Especially the part of the node that reads "it does not provide a reliable type testing mechanism for other kinds of built-in ... objects"
. Does that mean that future built-ins can't be tested with this method?
Let's use a concrete example.
If I want to ensure an object I have received from an unknown source is a String
object (an actual constructed String
object, not a primitive string), I could do:
if (Object.prototype.toString.apply(unknownObject) != '[object String]')
throw new TypeError('String object expected.');
This lets me know if unknownObject
is a String
object no matter what frame it was constructed in.
My question is, should this be the approach I take moving forward into ES6? Or is there an alternative? Something like Object.getNativeBrandOf
?
3. Since [[NativeBrand]]
seems like it won't include future types of objects, how would one test for these objects?
Will this work?
if (Object.prototype.toString.apply(unknownObject) != '[object Symbol]')
throw new TypeError('Symbol expected.');
...assuming Symbol
is the eventual name for Private Names.
Should I use this?
if (Object.prototype.toString.apply(unknownObject) != '[object WeakMap]')
throw new TypeError('WeakMap expected.');
... or something else?
The reason I ask is I am currently writing code that I want to be able to transition as easily as possible to ES6 in a year or two when possible. If there is a replacement for Object.prototype.toString
, then I can just shim it in and continue from there. Thanks!
Update
benvie's answer provided me with the correct term to search for and understand the answer to my questions.
I found an email from Allen Wirfs-Brock on es-discuss concerning this issue.
Here's what I found, for anyone else asking the same questions:
1. Do Chrome and Firefox behave correctly?
Yes, why is explained below.
2. Is there an alternative to Object.prototype.toString
?
As it is now, there will be a couple "alternatives" in the sense of possibilities, but not in the sense of replacements.
a. Using the @@toStringTag
symbol. However, my understanding is that Object.prototype.toString
should still probably be used. @@toStringTag
is provided to allow extending the results that can be returned from Object.prototype.toString
. If you have a prototype you would like to add your own string tag to, you could use @@toStringTag
to set the value to any string. Object.prototype.toString
will return this value except in the case where this value is one of the ES5 built-ins, in which case the string tag will be prepended with '~'.
b. Using private symbols on user-defined objects. I read one email promoting this as the best way to do the same type of check on a user-defined object. However, I don't see how that really settles the issue, as I fail to understand how it could be a cross-frame solution and it doesn't let you check against ES6 built-ins.
So even though there are some alternatives, it's good to stick with Object.prototype.toString
now and going forward, with one caveat:
It'll work to make sure you have an ES5 built-in, such as String
, but it won't be fool-proof to make sure you have an ES6 built-in because they can be spoofed with @@toStringTag
. I'm not sure why this is, and I may be missing something, or it could change as the spec evolves.
3. Since [[NativeBrand]]
seems like it won't include future types of objects, how would one test for these objects?
As mentioned above, Object.prototype.toString
can still be used on ES6 built-ins, but it's not fool-proof as it can be spoofed by anyone with access to the @@toStringTag
symbol. However, maybe there shouldn't be a fool-proof method, since Object.prototype.toString(weakmap) == '[object WeakMap]'
doesn't mean that weakmap instanceof WeakMap
(and it shouldn't!). The weakmap
could have come from another frame, or it could be a user-created weakmap-like object. The only thing you really know is it reports to be functionally equivalent to a WeakMap.
It does seem to beg the question why you can't have a user-defined object which reports to be functionally equivalent to a String
or Array
(sans the prefixed "~"
).
This is currently a moving target in the ES6 spec. For the existing set of objects, the existing mechanics are maintained for various reasons including compatibility. In the most recent ES6 spec, published October 26th, you can find some hints of the potential future direction
15.4.6.2.4 ArrayIterator.prototype.@@toStringTag
The initial value of the @@toStringTag property is the string value "Array Iterator".15.14.5.13 Map.prototype.@@toStringTag
The initial value of the @@toStringTag property is the string value "Map".
You can find the original discussion that originated this in this thread on es-discuss
来源:https://stackoverflow.com/questions/13151643/access-nativebrand-class-in-es6-ecmascript-6