Array.prototype.slice web-compat issue?
Very interesting. Here is one of the test cases André is talking about.
var g = newGlobal("new-compartment"); //or any non-standard mechanism to get a handle on another realm's global object
g.a = g.Array(10);
print(g.a instanceof Array);
g.a = Array.prototype.slice(g.a);
print(g.a instanceof Array);
-- Output ES<6: false, true Output ES6: false, false
Also note that if the instanceof
tests above were replaced with Array.isArray(g.a)
you would get all trues in both ES5 and ES6. instanceof
is not a reliable test for array-ness when objects flow between realms..
The problem is that in ES<6 slice
always returned a new Array
instance using the Array
of the realm associated with the invoked slice function. In ES6 slice
returns an object that is determine based upon the actual this value passed to slice
. In the default case like above, this will be the a new Array
instance using the Array
of the realm associated with the this value.
The reason for this change is to enable slice
to behave rationally for Array
subclasses. For example:
class SubArray extends Array {
get isSubArray() {return true}
}
let sa = new SubArray(10);
let sal = sa.slice(1);
print(sa.isSubArray); //true for Es6 slice, undefined for ES5 slice
print(sal instanceof SubArray); //true for ES6 slice spec. false for ES5 slice spec.
print(sal instanceof Array); //true for ES6. true for ES5
The way that the result object creation works in the ES6 spec. is approximately:
slice(start, end) {
...
let result = new this.constructor(); //create an instance of the same "class" as the this value
...
}
Continuing to allow slice
(and most other Array.prototype
methods) to work as expected with subclasses and still preserving ES5 cross-realm instanceof
compatability for these cases is at best hackish. Something like:
slice(start, end) {
...
let resultConstructor = this.constructor;
if (realm(resultConstructor) !== thisFunctionsRealm) {
if (isIntrinsicArrayConstrutorForAnyRealm(resultConstructor) then resultConstructor = thisFunctionsReam.intrinsics("Array");
}
let result = new resultConstructor();
...
}
I guess I could put mechanism to support this into the spec...
However, do we actually understand what the real world use case that tripped over this in FF? It's easy to write a test case that detects this difference but that sort of instanceof
test doesn't seem to make much sense for production code.
On Wed, Aug 28, 2013 at 10:19 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
The problem is that in ES<6
slice
always returned a newArray
instance using theArray
of the realm associated with the invokedslice
function. In ES6slice
returns an object that is determine based upon the actual this value passed toslice
. In the default case like above, this will be the a newArray
instance using theArray
of the realm associated with the this value.
!
This is a hazardous change for SES-style security. For example, I've just
taken a quick look at our (Caja) codebase and found a place where
Array.prototype.slice.call(foo)
is used to obtain a “no funny business”
array (i.e. doesn't have side effects when you read it) and another where
it's used to obtain an array which must be in the caller's realm. These
would be easy enough to replace with a more explicit operation, but I
wanted to point out that this is not a harmless change.
On Aug 29, 2013, at 10:51 AM, Kevin Reid wrote:
This is a hazardous change for SES-style security. For example, I've just taken a quick look at our (Caja) codebase and found a place where
Array.prototype.slice.call(foo)
is used to obtain a “no funny business” array (i.e. doesn't have side effects when you read it) and another where it's used to obtain an array which must be in the caller's realm. These would be easy enough to replace with a more explicit operation, but I wanted to point out that this is not a harmless change.
In the Array.prototype.slice.call(foo)
use case what is foo
? Is it known to be an Array
? Are you saying this is how you clone an Array
?
For you second use case, that sounds like it is contrary to what is implicitly assume by ES5. For ES5, every built-in is assume to be associated with a realm when it is created and any references to built-ins by a built-in are assume to use the same realm as the referencing built-in. So something like:
var newArray = slice.call( [ ] );
in ES5, should return a new Array
instances based upon the same Realm as the slice function that was invoked. It is not necessarily the same as the caller's realm.
Array literals are guaranteed to be create an array instance in the same realm as the code that evaluates the literal.
On Thu, Aug 29, 2013 at 12:56 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
In the
Array.prototype.slice.call(foo)
use case what isfoo
? Is it known to be anArray
? Are you saying this is how you clone anArray
?
Sorry, both are of that form, if I was unclear. When we want to simply clone an existing array, belonging to a secured realm, I think we generally use slice as a method, and there is no security property there.
Of the two cases I refer to, one is a function (the trademarking stamp()
)
which takes a list of objects as a parameter and needs to ensure that
successive stages of processing operate on exactly the same set of objects
and do not trigger any side effects in the list's implementation. Here,
realm is irrelevant but the list's implementation must be relied on, so in
practice we want an Array
from stamp()
's own realm.
The other case is one where it is a cross-frame protocol and we specifically want an object which belongs to 'our own' realm because its prototypes are frozen and specially extended, whereas the calling realm's prototypes notably are not frozen (it's outside of the shiny happy sandbox) and therefore constitute a risk to least-authority programming which we want to stop at the boundaries. (Note for MarkM: It's actually a little bit more complicated than this, but the details are irrelevant to the principle.)
For you second use case, that sounds like it is contrary to what is implicitly assume by ES5. For ES5, every built-in is assume to be associated with a realm when it is created and any references to built-ins by a built-in are assume to use the same realm as the referencing built-in. So something like:
var newArray = slice.call( [ ] );
Sorry, when I said "the caller" I meant this particular caller, the
function in our codebase which contains Array.prototype.slice
in its
source text and therefore does call the slice belonging to its own realm. I
apologize for the particularly misleading phrasing.
for both cases, are you using Array.isArray
to determine that you are operating upon an array?
what would be the appropriate thing to happen (all things considered) in a world where subclasses of Array
exist?
On Thu, Aug 29, 2013 at 2:21 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
for both cases, are you using
Array.isArray
to determine that you are operating upon an array?
In both cases, all we want is the user-specified set of values, to store or
operate on. So, in accordance with JavaScript idiom, we expect it to be
array-like, but (since we are multi-frame code) not necessarily an Array
from this frame.
Given this, there is no particular reason to perform an isArray
test,
unless we wanted to do type checks for linting purposes ("you passed a Foo
,
not an array of Foo
s; you probably made a mistake"), and we don't.
what would be the appropriate thing to happen (all things considered) in a world where subclasses of
Array
exist?
I don't have any examples to work from; I would think there is value in permitting them to be used to carry the intended set-of-values if the code calling our code uses them, but I do not see how any subclass could have a custom behavior which would be appropriate or useful to preserve rather than discarding in these two cases, or any other case where the array being passed is used in 'functional' fashion (immediately reading as opposed to either retaining it to look at later or mutating it).
(I admit I favor composition/delegation over inheritance, for public
interfaces, and therefore dislike the notion of working with subclasses of
concrete built-ins. But one could also consider, for example, the reasons
why java.lang.String
is final.)
Kevin Reid wrote:
(I admit I favor composition/delegation over inheritance, for public interfaces, and therefore dislike the notion of working with subclasses of concrete built-ins.
+∞
But one could also consider, for example, the reasons why java.lang.String is final.)
One could also view that as a total failure of the java.lang.String
designers/implementors.
researcher.ibm.com/files/us-bacon/Corwin03MJSlides-Grove.pdf
However, do we actually understand what the real world use case that tripped over this in FF?
Somewhat. See bugzilla.mozilla.org/show_bug.cgi?id=683140
Or do you mean what exactly in Jira is doing the instanceof tests?
On Sep 9, 2013, at 7:58 AM, Boris Zbarsky wrote:
However, do we actually understand what the real world use case that tripped over this in FF?
Somewhat. See bugzilla.mozilla.org/show_bug.cgi?id=683140
Or do you mean what exactly in Jira is doing the instanceof tests?
Yes, that's what I meant. It's clear what the test is doing but I'd like to understand a bit better what the application logic thinks it's doing.
On 9/9/13 12:01 PM, Allen Wirfs-Brock wrote:
On Sep 9, 2013, at 7:58 AM, Boris Zbarsky wrote:
However, do we actually understand what the real world use case that tripped over this in FF?
Somewhat. See bugzilla.mozilla.org/show_bug.cgi?id=683140
Or do you mean what exactly in Jira is doing the instanceof tests?
Yes, that's what I meant. It's clear what the test is doing but I'd like to understand a bit better what the application logic thinks it's doing.
It's a bit sad that the bug doesn't have info on that. :(
Jan, do you recall what the exact issue was with Jira here, by any chance?
----- Original Message -----
On 9/9/13 12:01 PM, Allen Wirfs-Brock wrote:
Yes, that's what I meant. It's clear what the test is doing but I'd like to understand a bit better what the application logic thinks it's doing.
It's a bit sad that the bug doesn't have info on that. :(
Jan, do you recall what the exact issue was with Jira here, by any chance?
Unfortunately not :( IIRC an iframe for one of the widgets passed an Array to its parent frame, and the parent frame used slice + instanceof, but I don't remember the details after two years...
Jan
I see one of two problems mentioned in my other topic is already discussed here so I'd like to add this question that will most likely break the web:
- will
[].slice.call(arguments)
produce a pointless instanceof Arguments ? - will
[].slice.call(document.querySelectorAll('*'))
produce an instanceof Static NodeList ?
AFAIK 99% of libraries out there believe that slice.call will return an
Array of the current realm.
Wouldn't be wise to distinguish between explicit
Array.prototype.call(context)
VS context.slice()
so that only latter
one will produce the magic ES6 would like to add ?
Thanks for any clarification.
On Sep 10, 2013, at 10:37 AM, Andrea Giammarchi wrote:
AFAIK 99% of libraries out there believe that slice.call will return an Array of the current realm.
Can you explain why the Realm makes a difference to them? Is it because they are doing instanceof Array tests rather than Array.isArray?
As short answer, let me reformulate: AFAIK 99% of libraries out there believe that slice.call will return an Array, no matter which realm. In ES6 I see there's too much magic involved because of subclassing possibility.
Long story:
Historically, instanceof
has been cross browser/engine the fastest way to
understand if an object is an Array, compared with both
Object#toString.call(arr)
and/or Array.isArray
plus the web does not
really pass Arrays between iframes anymore thanks to postMessage
and
other serialization mechanisms where, once deserialized, instanceof
will
do the trick (also JSON.parse creates Arrays of the current realm)
Caja is a very-very-very specific use case that does not reflect majority
of websites so instanceof
worked most of the time for most of the site
but regardless, Caja believes slice.call wil generate an Array
that will
be instanceof Array
and this is a valid assumption for every sandboxed
code that believes in instanceof
rejecting all external "unknown"
instances.
Finally, Object.prototype.toString.call(object)
instead of instanceof
is abusing a hack that is also slow ... subclassing is not only about
Array
.. or is it? 'cause in such case I'd rather suggest a new
constructor called ArrayLike
or ArrayObject
, easier to polyfill and
ready to be subclassed with all the needed magic (as reflection of Array
behavior)
Off Topic: I've personally tried to subclass Array since IE5 so this is a welcome thing but does not seem to be that practical as specced now considering existing code.
On Sep 10, 2013, at 11:12 AM, Andrea Giammarchi wrote:
As short answer, let me reformulate: AFAIK 99% of libraries out there believe that slice.call will return an Array, no matter which realm. In ES6 I see there's too much magic involved because of subclassing possibility.
Presumably this is a 100% reasonable expectation today, because that's that the ES<=5.1 specs say. However, often do they depend upon the value being a direct instance of Array as opposed to being an instance of a subclass of an array, assuming that the subclass how all the observable characteristics of an Array instances including answering true to Array.isArray and maintaining the Array invariant.
In other words, those libraries have never encountered a true subclass of Array. When they do, is anything bad going to happen if the result object is also a subclass of Array?
BTW, I think it is useful to distinguish this use case from applying slice to non Array.isArray objects or using slice as a strange way to create a new Array instance.
My main concern was about non Array
objects such instances of Arguments
or NodeList
or others Array_ish_ instances where to work with .slice()
all we need is a length
property.
If these will return not as instanceof
current Array
realm most of the
code will be broken.
If .slice()
will be that smart as I expect, I wonder why nobody is
mentioning .concat()
as able to concatenate every Array_ish_ and produce
an instanceof
the original object constructor that invoked that concat
call.
ArrayIsh.prototype.concat([]) instanceof ArrayIsh
Last doubt would be about Array.isArray
and the inability to distinguish,
if not via Object#toString
or instanceof
between two different
ArrayIsh
objects ... so that instanceof
will be not usable for
ArrayIsh
and at the same time the best solution to understand what kind
of ArrayIsh
it is so that extra, enriched, methods can be used.
Thoughts?
On Tue, Sep 10, 2013 at 1:37 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
I see one of two problems mentioned in my other topic is already discussed here so I'd like to add this question that will most likely break the web:
- will
[].slice.call(arguments)
produce a pointless instanceof Arguments ?
No, because you can't construct new Arguments()
(Arguments is undefined),
leaving A undefined at step 15.c, which means step 16 evaluates to true,
which leads to 16.a and that produces an Array.
- will
[].slice.call(document.querySelectorAll('*'))
produce an instanceof Static NodeList ?
This is a good question, if NodeList has an internal [[Construct]] it will pass the IsConstructor test and then throw a TypeError exception as soon as 15.c.i occurs. Based on my interpretation of the steps in OrdinaryConstruct, it wouldn't survive. Maybe Allen can clarify?
Also, this is the exact pain point case that Array.from was designed to address, but existing code shouldn't break in the interim.
the polyfill for Array.from is based on slice 'cause slice works like that
since, well, ever.
That's why I've raised some concern about a change on slice ... I'd rather
extend a new ArrayObject
instead of touching Array
also because
polyfills will make common/borrowed slice operations painfully slow.
My point is: Array
has never been subclassed and nobody is expecting to
be able to do that with current semantics/code/libraries if not redefining
and delegating.
ES6 introduces new syntax that should desugar to what every browsers know already ... it's "easy", when possible, to polyfill a new global class ... it will make polyfills very painful otherwise.
I can already imagine ES6 shim overwriting all Array.prototype
methods to
obtain this new behavior ... it would be much easier to do this instead:
function ArrayObject() {}
ArrayObject.prototype.slice = function () {
return Object.setPrototypeOf(
Array.prototype.slice.call(this, arguments),
Object.getPrototypeOf(this)
);
};
... apply for gosh sake ...
function ArrayObject() {}
ArrayObject.prototype.slice = function () {
return Object.setPrototypeOf(
Array.prototype.slice.apply(this, arguments),
Object.getPrototypeOf(this)
);
};
On Tue, Sep 10, 2013 at 6:42 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
the polyfill for Array.from is based on slice 'cause slice works like that since, well, ever.
You might've made a polyfill like that, but I think it's ok to admit that the approach was naive. This was mine, written before the mapFn was spec'ed, no slice: gist.github.com/rwaldron/3186576
Anyway, I'm still with you on making sure that behaviour doesn't break.
On Sep 10, 2013, at 3:24 PM, Rick Waldron wrote:
On Tue, Sep 10, 2013 at 1:37 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote: I see one of two problems mentioned in my other topic is already discussed here so I'd like to add this question that will most likely break the web:
- will
[].slice.call(arguments)
produce a pointless instanceof Arguments ?No, because you can't construct
new Arguments()
(Arguments is undefined), leaving A undefined at step 15.c, which means step 16 evaluates to true, which leads to 16.a and that produces an Array.
Exactly, as currently spec'ed slice applied to an arguments object or any Array.isArray false produce an instance of Array from the same realm as the slice function.
- will
[].slice.call(document.querySelectorAll('*'))
produce an instanceof Static NodeList ?This is a good question, if NodeList has an internal [[Construct]] it will pass the IsConstructor test and then throw a TypeError exception as soon as 15.c.i occurs. Based on my interpretation of the steps in OrdinaryConstruct, it wouldn't survive. Maybe Allen can clarify?
See %TypedArray%.prototype.slice people.mozilla.org/~jorendorff/es6-draft.html#sec-22.2.3.24 which in some subtle ways is a cleaner and more general versions of Array slice.
But, in either version I'm not very happy with just assuming that this.constructor(n) produces a new this.constructor instance of size n. I'm seriously thinking about specify a @@ method on Arrays and other collections that allocates a new instance of a given size. perhaps Array@@newSized
In that case, NodeList could define @@newSized and it would work with either version of slice.
Also, this is the exact pain point case that Array.from was designed to address, but existing code shouldn't break in the interim.
Right, there are several ways in ES6 to force creation of an initialized Array include [...collection] and Array.from
And for existing code, it should just keep working although we have to address the Realm issue.
The other less obvious question is what should happen when old code is combined with new code that uses Array subclassing. My position, is that it will usually just work, but its is essentially newly new code and there may be a few edge cases that the older parts of the code didn't anticipate. Test is probably required.
Rick I think I've often seen this which is not that naive accordingly with
ES 5.1 down to ES 3 specs, IMO .. it worked with DOM, arguments, and
everything with a length
till now.
Array.from||(Array.from=function(a){return Array.prototype.slice.call(a)});
Allen the %TypedArray%
is a good example because this is false: new Float32Array instanceof Array
... that constructor has even the suffix
with the Array
word but is not an Array
at all.
Array.isArray(new Float32Array)
is again false indeed.
I also think this magic should somehow be something new, hoping nobody will think about creating polyfills for all Array methods because once again that will be very bad for the web/mobile/ARM world, IMO.
Thanks for all answers though.
On Sep 10, 2013, at 4:11 PM, Andrea Giammarchi wrote:
Rick I think I've often seen this which is not that naive accordingly with ES 5.1 down to ES 3 specs, IMO .. it worked with DOM, arguments, and everything with a
length
till now.
Array.from||(Array.from=function(a){return Array.prototype.slice.call(a)});
Allen the
%TypedArray%
is a good example because this is false:new Float32Array instanceof Array
... that constructor has even the suffix with theArray
word but is not anArray
at all.
Array.isArray(new Float32Array)
is again false indeed.I also think this magic should somehow be something new, hoping nobody will think about creating polyfills for all Array methods because once again that will be very bad for the web/mobile/ARM world, IMO.
Which magic are you talking about. Array.isArray is defined for ES6 to test whether an object has the length invariant automatically enforced. That's really the only thing that makes an Array instance special (or in ES6-speak, exotic). Float32Arrays do not have that length invariant because their length is fixed.
What do you think people are actually testing for when they do Array.isArray?
right now? the [[Class]]
such as '[object Array]'
... and the magic is
this smarter slice.
I insist this would be way better and already possible today:
(function(Object, ArrayPrototype){
if (typeof ArrayObject !== 'undefined') return;
ArrayObject = function(){};
for(var
modify = [
'concat',
'copyWithin',
'filter',
'map',
'slice',
'splice'
],
keys = Object.getOwnPropertyNames(ArrayPrototype),
create = function(method) {
return function() {
return Object.setPrototypeOf(
method.apply(this, arguments),
Object.getPrototypeOf(this)
);
};
},
i = keys.length,
current, key;
i--;
) {
key = keys[i];
current = Object.getOwnPropertyDescriptor(ArrayPrototype, key);
if (~modify.indexOf(key)) current.value = create(current.value);
Object.defineProperty(ArrayObject.prototype, key, current);
}
}(Object, Array.prototype));
where any class that extends ArrayObject
will have the new behavior and
less problem with the past.
var a = new ArrayObject;
a.push(1, 2, 3);
a.slice() instanceof ArrayObject; // true
br
On Sep 10, 2013, at 4:38 PM, Andrea Giammarchi wrote:
right now? the
[[Class]]
such as'[object Array]'
... and the magic is this smarter slice.
And what does [[Class]] == "Array" actually tell you? Primarily that the Array length invariant is automatic enforced.
You seems to be conflating two orthogonal concepts:
- whether an object is an exotic array object (ie, enforces the length invariant)
- whether an object inherits from Array.prototype
Even in ES5+web reality, these are separable concepts:
var exotic = [ ]; exotic.proto=Object.prototype; //exotic has the array invariant, Array.isArray(exotic) is true, exotic instanceof Array is false, exotic does not expose any Array methods
function ArrayLIkeSubclass() { this.length = 0; } ArrayLikeSubclass.prototype = { proto: Array.prototype, constructor: ArrayLikeSubclass }
var alike= new ArrayLikeSubclass(); //alike doesn't have array invariant, Array.isArray(alike) is false, alike instanceof Array is true, alike inherits all of the Array methods
function ArrayExoticSubclass() { var obj = [ ]; obj.proto = Object.getPrototypeOf(this); return obj; } ArrayExoticSubclass.prototype = { proto: Array.prototype, constructor: ArrayExoticSubclass }
var asub= new ArrayExoticSubclass(); //asub has the array invariant, Array.isArray(asub) is true, asub instanceof Array is true, and it inherits all of the Aarray methods
I insist this would be way better and already possible today:
(function(Object, ArrayPrototype){ if (typeof ArrayObject !== 'undefined') return; ArrayObject = function(){}; for(var modify = [ 'concat', 'copyWithin', 'filter', 'map', 'slice', 'splice' ], keys = Object.getOwnPropertyNames(ArrayPrototype), create = function(method) { return function() { return Object.setPrototypeOf( method.apply(this, arguments), Object.getPrototypeOf(this) ); }; }, i = keys.length, current, key; i--; ) { key = keys[i]; current = Object.getOwnPropertyDescriptor(ArrayPrototype, key); if (~modify.indexOf(key)) current.value = create(current.value); Object.defineProperty(ArrayObject.prototype, key, current); } }(Object, Array.prototype));
where any class that extends
ArrayObject
will have the new behavior and less problem with the past.
var a = new ArrayObject; a.push(1, 2, 3); a.slice() instanceof ArrayObject; // true
The above is essentially equivalent to the following ES6 code:
class ArrayObject extends Array { slice(...args) {return this.constructor.from(super(...args))} //do the same thing for concat, map, etc. }
except that, Array.isArray(new ArrayObject) is true
(new Array).slice() instanceof Array //true (new Array).slice() instanceof ArrayObject //false Array.isArray(new Array) //true (new ArrayObject).slice instanceof ArrayObject //true (new ArrayObject).slice instanceof Array //true Array.isArray(new ArrayObject) //true
All the ES6 spec. does for slice and friends is to make the subclass over-rides unnecessary. You will get the exact same results, without the over-rides.
Allen if you put the word Array
but you define it differently then maybe
it's not me misunderstanding ... Float32Array is not an Array in current
ES, that's "fun enough" if you ask me.
In any case, if that was the problem, being an Array, the solution is quite simple (recycling code, could be better):
(function(Object, ArrayPrototype){
if (typeof ArrayObject !== 'undefined') return;
// the only difference
(ArrayObject = function(){}).prototype = [];
for(var
modify = [
'concat',
'copyWithin',
'filter',
'map',
'slice',
'splice'
],
keys = Object.getOwnPropertyNames(ArrayPrototype),
create = function(method) {
return function() {
return Object.setPrototypeOf(
method.apply(this, arguments),
Object.getPrototypeOf(this)
);
};
},
i = keys.length,
current, key;
i--;
) {
key = keys[i];
current = Object.getOwnPropertyDescriptor(ArrayPrototype, key);
if (~modify.indexOf(key)) {
current.value = create(current.value);
}
Object.defineProperty(ArrayObject.prototype, keys[i], current);
}
}(Object, Array.prototype));
and no old code will ever be affected so that in ES6 you can
class Whatever extends ArrayObject {
// that's it
}
and if you chose to extend Array
the slice will produce an Array
I think this is a better alternative to potentially broken code ... but I might be wrong.
Let's wait for this change to break out there, maybe it will not.
Cheers
On 9/10/13 7:32 PM, Allen Wirfs-Brock wrote:
What do you think people are actually testing for when they do Array.isArray?
It varies, but some things I've seen people test for via "is array" methods:
-
Polymorphic methods that take an "array" or "something else". Just looking at a random sampling of JS right now, I see a function that takes as its first argument a URI or array of URIs, a function that takes a colorspace or array of colorspaces, a function that takes a callback or array of callbacks.
-
Functions (e.g. jQuery has some) that deep-clone object graphs and wants to turn arrays into [] in the clone while turning other objects into {}. A special case of this is implementations of structured cloning, or indeed JSON.stringify.
-
Sanity checking of input parameters to functions. One could argue that this is misguided, or that it saves debugging time; I've seen people make both arguments.
On 9/10/13 8:50 PM, Allen Wirfs-Brock wrote:
And what does [[Class]] == "Array" actually tell you?
What will happen when you structured clone or JSON.stringify the object, the length invariant, and what happens if you pass it as an argument to Array.prototype.concat are the main things.
- whether an object inherits from Array.prototype
That one tells you nothing about any of the four things above, indeed.
On Sep 10, 2013, at 6:08 PM, Boris Zbarsky wrote:
On 9/10/13 8:50 PM, Allen Wirfs-Brock wrote:
And what does [[Class]] == "Array" actually tell you?
What will happen when you structured clone or JSON.stringify the object, the length invariant, and what happens if you pass it as an argument to Array.prototype.concat are the main things.
Exactly the same thing as ES5 (its all in the ES6 spec.). "obj is an exotic array object" is ES6-speak that means the same things as the ES5-speak "obj's [[Class]] is "Array".
In either case, all objects that pass one of the above predicates implements the array length invariant.
ES6 doesn't prescribe how an implementation goes about tagging an object as an "exotic array object". ES5 didn't prescribe how an implementation actually encoded the [[Class]] internal property.
This test case from SpiderMonkey failed when I applied the latest spec changes to my ES6 test implementation. Based on the bug report at 2, this might be another web-compatibility issue - but is that really the case? Any input appreciated!