Array.prototype.slice web-compat issue?

# André Bargull (4 years ago)

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!

# Allen Wirfs-Brock (4 years ago)

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.

# Kevin Reid (4 years ago)

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 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.

!

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.

# Allen Wirfs-Brock (4 years ago)

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.

# Kevin Reid (4 years ago)

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 is foo? Is it known to be an Array? Are you saying this is how you clone an Array?

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.

# Allen Wirfs-Brock (4 years ago)

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?

# Kevin Reid (4 years ago)

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 Foos; 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.)

# Brendan Eich (4 years ago)

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‎

# Boris Zbarsky (4 years ago)

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?

# Allen Wirfs-Brock (4 years ago)

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.

# Boris Zbarsky (4 years ago)

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?

# Jan de Mooij (4 years ago)

----- 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

# Andrea Giammarchi (4 years ago)

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:

  1. will [].slice.call(arguments) produce a pointless instanceof Arguments ?
  2. 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.

# Allen Wirfs-Brock (4 years ago)

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?

# Andrea Giammarchi (4 years ago)

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.

# Allen Wirfs-Brock (4 years ago)

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.

# Andrea Giammarchi (4 years ago)

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?

# Rick Waldron (4 years ago)

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:

  1. 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.

  1. 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.

# Andrea Giammarchi (4 years ago)

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)
  );
};
# Andrea Giammarchi (4 years ago)

... apply for gosh sake ...

function ArrayObject() {}
ArrayObject.prototype.slice = function () {
  return Object.setPrototypeOf(
    Array.prototype.slice.apply(this, arguments),
    Object.getPrototypeOf(this)
  );
};
# Rick Waldron (4 years ago)

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.

# Allen Wirfs-Brock (4 years ago)

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:

  1. 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.

  1. 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.

# Andrea Giammarchi (4 years ago)

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.

# Allen Wirfs-Brock (4 years ago)

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 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.

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?

# Andrea Giammarchi (4 years ago)

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

# Allen Wirfs-Brock (4 years ago)

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:

  1. whether an object is an exotic array object (ie, enforces the length invariant)
  2. 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.

# Andrea Giammarchi (4 years ago)

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

# Boris Zbarsky (4 years ago)

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:

  1. 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.

  2. 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.

  3. 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.

# Boris Zbarsky (4 years ago)

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.

  1. whether an object inherits from Array.prototype

That one tells you nothing about any of the four things above, indeed.

# Allen Wirfs-Brock (4 years ago)

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.