There have been many versions of 'isArray' tests but to date none have
been reliable, cross-frame and sanctioned by the ECMAScript
specification (in the sense of being tests that the specification
required to have correct outcomes).
The best to date has employed what is apparently to be called the "The
Miller Device" (after Mark Miller, who thought it up). It uses the
specified characteristics of - Object.prototype.toString - and (in this
context) would take the form:-
function isArray ( obj ) {
return Object.prototype.toString.call(obj) === "[object Array]";
}
The - Object.prototype.toString - method is specified as taking the
internal [[Class]] property of an object and creating/returning a string
by inserting it between '[object ' and ']'. Arrays are required to have
a [[Class]] of "Array" and all other built-in/native objects are
required to have other values in their [[Class]] properties.
The problem with this method (and thought it is obvious I have not yet
seen anyone point it out) is that while ECMA 262 does specify the
precise value of [[Class]] properties for all built-in and native
objects it does not (and could never) specify that value for host
objects. The spec (ECMA 262 3rd Ed, Section 8.6.2) says: "The value of
the [[Class]] property of a host object may be any value, even a value
used by a built-in object for its [[Class]] property". Thus, it is
entirely within the specification for any built in object to have a
[[Class]] property, and for the value of that property to be "Array" (or
"Function", which is significant when this method is applied to
'isFunction' tests). Thus, an object that has a [[Class]] property with
a value that is not 'Array' must not be an Array, but an object with a
[[Class]] property that is "Array" might be an Array _or_ some host
object.
Now, step 4 in the algorithm for the - Array.prototype.concat - method
reads:-
4. If E is not an Array object go to step 16.
-, which requires ECMAScript implementations to be able to discriminate
between an object being an Array and its not being an Array. And if the
implementation must be able to make that discrimination then it should
be possible to exploit that ability to make the same discrimination for
ourselves.
The implications of step 4 in the - concat - algorithms are that if you
pass an object as an argument to the - concat - method, if that object
is an Array then its 'array index' members are added to a new array
created by the method, and if it is not an array then the object itself
is added to that new array. Thus the test would be to pass the test
subject as an argument to the - concat - method called on an empty
array.
If the resulting array's length was anything but 1 then the test subject
must have been an Array (an array with a length other than 1). If the
resulting array's length was 1 but the only value in that array was not
the test subject then the test subject also must have been an Array.
Having identified all arrays with lengths that are not 1 and all arrays
that have a length of 1 but do not have themselves as their only
element, the remaining task is to discriminate the one remaining Array
possibility form all other objects.
The remaining Array possibility would be an Array along the lines of:-
var testAr = []
testAr[0] = testAr;
- but a non-Array along the lines of:-
var testObj = {length:1};
testObj[0] = testObj;
- is also a possibility. So the remaining testing checks to see if the
test subject 'looks' like it might be an Array (in the form of) the one
element Array above, and then uses the Array's special [[Put]] method to
make the final discrimination for that last case.
So my proposed 'isArray' function goes:-
function isArray(a){
var ar, ret = false;
if(
(typeof a == 'object')&&
(a)&&
(typeof a.length == 'number')
){
ar = [].concat(a);
if(
(ar.length == 1)&&
(ar[0] === a)
){
if(
(a.length == 1)&&
(a[0] === a)&&
(typeof a.hasOwnProperty == 'function')&&
(a.hasOwnProperty('length'))&&
(a.hasOwnProperty('0'))
){
a.length = 0;
if(a[0] != a){
a[0] = a;
ret = (a.length === 1);
}
a.length = 1;
}
}else{
ret = true;
}
}
return ret;
}
- (There is a fully commented version at the end of this post.)
For a host object to pass this test (return true) it would have to have
a '0' property (itself, not inherited) that referred to itself, a
numeric length property with the value 1 and be such that assignments to
its - length - property had Array-like side effects, and also that
assigning to its '0' property had array-like side effects. A possibility
but if that very unusual host object does exist anywhere it probably can
be safely treated as an array.
There remains the possibility of encountering a host object that passes
the earlier tests and then throws an exception during later tests. It is
theoretical possibility, but I don't think may host objects are going to
be getting past the - (a[0] === a) - line as having a '0' property that
refers to itself would be an extremely unlikely characteristic of any
object.
Despite this test being sanctioned by ECMA 262 3rd Ed. testing it did
expose an implementation bug in IE browsers (6 and 7). One obvious test
was to define the - isArray - function in a page that contained an
IFRAME and have the page loaded in the frame pass the function arrays
that 'belonged' to that frame (through - top.isArray(testSubject); -).
Doing that worked fine (on IE Opera, Safari, Firefox and Chrome (and if
it did not work that would be an implementation bug)). The next obvious
test was replace the IFRAME with a - window.open - call and have the
page loaded in the new window call the test function as -
opener.isArray(testSubject); -, and on IE that call failed to correctly
identify Array (asserting false for array arguments).
Looking into this it seems that inter-window exchanges of objects get
wrapped in additional objects. Presumably this is for security reasons,
but you get some strange effects from it, such as the - hasOwnProperty -
method of the Arrays passed in from the external widow produce 'object'
when tested with - typeof -, while the 'local' array's methods produce
'function'.
So does this mean that, although it has no basis in ECMA 262 for
Array/host object discrimination, the "The Miller Device" is the better
test? Unfortunately it does not because the "The Miller Device" is
victim of the same bug as -
Object.prototype.toString.call(arrayFromAnotherWindow)
- returns "[object Object]' in IE 6 and 7 (with similar implications for
the "The Miller Device" in 'isFunction' testing caused by the same
'wrapping' of 'remote' objects)
It is probably a good thing for everyone that pop-up blocking has
virtually eliminate cross-window scripting.
Richard Cornford.
// Fully Commented - isArray - source:-
function isArray(a){
var ar, ret = false;
/* If - typeof a - is not 'object' then - a - cannot be an Array.
The - null - value also has a typeof of 'object' so a type-
converting test excludes null.
Arrays have numeric - length - properties so if - a - does not
have such a property then it is not an Array.
*/
if(
(typeof a == 'object')&&
(a)&&
(typeof a.length == 'number')
){
/* The algorithm for the - concat - method of an array
includes a step 4 which reads:-
4. If E is not an Array object go to step 16.
- which is interesting because it expects an implementation
to 'know' that an object is an Array, rather than testing
some characteristic of that object to determine whether it
looks like an Array. Thus this step needs to be able to
avoid issues such as those with - x instanceof Array -,
which does not work across frames (as the identity of the
Array constructor (or rather its - prototype -) is not
necessarily the same in each frame) and it also produce
a positive assertion with objects that have Arrays on
their prototype chains (such objects would also inherit
all the array properties and method so duck-typing would
be fooled by them).
The effect of - concat - is that if its argument is not
an array then its single value is added to the returned
array, and if its argument is an array then each of that
array's ('array index') members are added to the returned
array in turn.
So if you - concat - the subject of the test to an empty
array and the result has a length that is not one then
the subject of the test must be an array.
*/
ar = [].concat(a);
/* However, if the - length - is one then the test subject
may still be an Array. If it is an Array then it must be
a one element array, and its first element will be the
one at index zero in the new array. So if the element at
index zero in the new array is not the test subject then
the test subject must be an array.
*/
if(
(ar.length == 1)&&
(ar[0] === a) //ar[0] may be primative so must use ===
){
/* If the test subject were an Array then it could only
be a one element array with a reference to itself as
its only member. Thus if its length is not one it
is not an Array, and if its zero indexed element is
not itself (the object) then it is not an Array.
Also, if the test subject is an Array then its -
length - and its - 0 - properties must be its own.
Note: an ES 3.1 version of this test would have to
verify that neither of the - length - or - 0 -
properties had 'getters' or 'setters' (else the
modifications later may have unknowable side effects
that could not be reversed here). For an Array
neither should.
*/
if(
(a.length == 1)&&
(a[0] === a)&& //a[0] may be primative so must use ===
(typeof a.hasOwnProperty == 'function')&&
(a.hasOwnProperty('length'))&&
(a.hasOwnProperty('0'))
){
/* The odds of getting here are extremely low. The
only possibilities are a single element array that
has a reference to itself as its single element or
an object with a '0' property that is a reference
to itself and a - length - property that is
numeric one.
If the test subject is an array then assigning zero
to its - length - will have the side effect of
deleting its zero indexed element. If it is not an
Array then that assignment would not have that
side effect.
The property modification on the test subject is
acceptable here because we have narrowed down to
the real edge cases, have determined that the
properties that will be modified are properties
of the test subject itself, and know precisely
which values those properties started with and
so can safely restore them.
*/
a.length = 0;
/* Previously - (a[0] === a) - was tested to be true,
so now if it is false we have the side effects that
are expected of a - length - assignment on an
Array, and then if - (a[0] != a) - is false the
test subject cannot be an Array.
*/
if(a[0] !== a){
/* The object has been modified so it should be
returned to its original state, and so the
deleted zero index property is resorted to
its original state.
*/
a[0] = a;
/* This assigning to the zero property should
also have a side effect on the - length -,
if it does not then the test subject is not
an Array. It is an Array if the length is
back to one, else it cannot be an Array.
*/
ret = (a.length === 1);
}
/* If we did not pass through the previous - if -
block (or the subject did not turn out to be an
Array in that block) then the subject's - length
- needs to be re-set to its original value,
which was one. This is harmless if we did
determine the subject to be an Array.
*/
a.length = 1;
}
}else{
ret = true;
}
}
return ret;
}
> While reading the ECMA spec (actually a draft for 3.1 but the algorithm
> is the same in 3) it occurred to me that the algorithm for the Array -
> concat - method offered a way in for a frame independent 'isArray' test
> function that could be fully sanctioned by ECMA 262. I have never had
> much use for such a test myself but the question has come up in the past
> and the possibilities should be considered.
>
> There have been many versions of 'isArray' tests but to date none have
> been reliable, cross-frame and sanctioned by the ECMAScript
> specification (in the sense of being tests that the specification
> required to have correct outcomes).
>
> The best to date has employed what is apparently to be called the "The
> Miller Device" (after Mark Miller, who thought it up). It uses the
> specified characteristics of - Object.prototype.toString - and (in this
> context) would take the form:-
>
> function isArray ( obj ) {
> return Object.prototype.toString.call(obj) === "[object Array]";
> }
I'm afraid that if you don't like
function isArray(obj)
{
return (obj.constructor == Array);
}
or
function isArray(obj, aArray)
{
return (obj.constructor == (aArray || Array));
}
with
isArray([], Array);
and the like, you will never be fully satisfied with the alternatives as
ECMAScript implementations are far too dynamic languages for a fully
reliable *and* efficient test.
> So my proposed 'isArray' function goes:-
> [...]
OMG.
> // Fully Commented - isArray - source:-
OMG.
PointedEars
Well, I am not really 'satisfied' by the whole idea. It's mostly used in
the context of emulating 'method overloading', which I am not in favour
of in general.
The functions above will not necessarily handle cross-frame Array
detection well, but that is only an issue if you have frames and want to
identify Arrays in that context. It is usually useful to recognise that
problems do not need to be solved if they are not problems that exist in
your particular context. It is the people who attempt to be general who
need to be interested in these things.
Because my code is 100% driven by the spec it should be reliable (at
least where implementation bugs don't enter the picture (such as the IE
bugs described, which impact on the alternative is just the same way)).
As far as 'efficient' goes, I should point out that the vast majority of
test subjects will be decided in very few actual operations. All the non
one element arrays are identified in 8 expressions, and most non-arrays
will not get past - (typeof a.length == 'number') -. The bulk of code is
there to accommodate the possibility of a one element array with a
reference to itself as its only element, and telling that object from a
non-Array that looks like it. Such an array is an extremely unlikely
possibility, as are objects that look like it. So there is very little
likelihood that the code that makes that discrimination will ever be
executed.
Richard.
> Thomas 'PointedEars' Lahn" wrote:
>> Richard Cornford wrote:
> <snip>
>> I'm afraid that if you don't like
>>
>> function isArray(obj)
>> {
>> return (obj.constructor == Array);
>> }
>>
>> or
>>
>> function isArray(obj, aArray)
>> {
>> return (obj.constructor == (aArray || Array));
>> }
>>
>> with
>>
>> isArray([], Array);
>>
>> and the like, you will never be fully satisfied with the
>> alternatives as ECMAScript implementations are far too
>> dynamic languages for a fully reliable *and* efficient
>> test.
> <snip>
>
> Well, I am not really 'satisfied' by the whole idea. It's mostly used in
> the context of emulating 'method overloading', which I am not in favour
> of in general.
>
> The functions above will not necessarily handle cross-frame Array
> detection well,
True. If we had a reliable enough way to refer to the Array object of the
calling global execution context, that would not be a problem.
> [...]
> Because my code is 100% driven by the spec it should be reliable (at
> least where implementation bugs don't enter the picture
Unfortunately, while your approach may be written entirely according to the
Specification (I have not had time to confirm that), it is prone to all
kinds of property modification due to the dynamic nature of the ECMAScript
language. ISTM that it is therefore best to use as little properties as
possible in such a test.
> [...]
> All the non one element arrays are identified in 8 expressions, and most
> non-arrays will not get past - (typeof a.length == 'number') -. The bulk
> of code is there to accommodate the possibility of a one element array
> with a reference to itself as its only element, and telling that object
> from a non-Array that looks like it.
s/bulk/bloat/, sorry.
PointedEars
But we don't.
>> [...]
>> Because my code is 100% driven by the spec it should be reliable
>> (at least where implementation bugs don't enter the picture
>
> Unfortunately, while your approach may be written entirely
> according to the Specification (I have not had time to confirm
> that), it is prone to all kinds of property modification due
> to the dynamic nature of the ECMAScript language.
I would like to see that demonstrated. As far as I can see the worst
outcomes would be:-
1. An undesirable side effect of transferring inherited -
length - and/or - 0 - properties to the object itself in the event that
the object looks like a one element array with a reference to itself as
its only element and functions assigned to - hasOwnProperty - (possibly
through inheritance) that give a false positive. The return value from
the - isArray - function should still correctly report false for such an
object.
2. A one element array that had a reference to itself as its only
element would be reported as not being an array if its -
hasOwnProperty - property was assigned a non-function value or a
function that returned false (possibly through inheritance).
If there is something worse than that I have not noticed it, and
would like to see it.
> ISTM that it is therefore best to use as little properties
> as possible in such a test.
It is, and I have. The - length -, - 0 - and - hasOwnProperty -
properties are the only ones used, and you only get to -
hasOwnProperty - (which is probably the biggest modification
vulnerability) if the object has a - 0 - property that refers to itself
(which you have to accept is a pretty unlikely eventuality).
>> [...]
>> All the non one element arrays are identified in 8 expressions,
>> and most non-arrays will not get past - (typeof a.length == 'number')
>> -. The bulk of code is there to accommodate the possibility of a
>> one element array with a reference to itself as its only element,
>> and telling that object from a non-Array that looks like it.
>
> s/bulk/bloat/, sorry.
The cost of trying to be general. (but the code could lose quite a few
bytes in re-formatting and is repetitive enough to zip well)
Richard.
Funny you mention it now. I wrote a blog post about cross-frame
`isArray` just few days ago [1]. It is essentially (as I have found out
later) what Mark M. demonstrated to Crockford.
I wrote generic `__getClass` back in June - when reading specs - but
never got a chance to write about such approach until now.
[...]
> The problem with this method (and thought it is obvious I have not yet
> seen anyone point it out) is that while ECMA 262 does specify the
> precise value of [[Class]] properties for all built-in and native
> objects it does not (and could never) specify that value for host
> objects. The spec (ECMA 262 3rd Ed, Section 8.6.2) says: "The value of
> the [[Class]] property of a host object may be any value, even a value
> used by a built-in object for its [[Class]] property". Thus, it is
> entirely within the specification for any built in object to have a
> [[Class]] property, and for the value of that property to be "Array" (or
> "Function", which is significant when this method is applied to
> 'isFunction' tests). Thus, an object that has a [[Class]] property with
> a value that is not 'Array' must not be an Array, but an object with a
> [[Class]] property that is "Array" might be an Array _or_ some host object.
This is my main concern as well [2]. Just like with `typeof` (which is
allowed to return implementation-dependent values) [[Class]]-based
`isArray` needs extensive testing across existent clients.
[snip `isArray` tests for later review]
> Object.prototype.toString.call(arrayFromAnotherWindow)
>
> - returns "[object Object]' in IE 6 and 7 (with similar implications for
> the "The Miller Device" in 'isFunction' testing caused by the same
> 'wrapping' of 'remote' objects)
That's not good at all.
I wonder if a check based on non-generic nature of
`Array.prototype.toString` returns "proper" result. The specs are pretty
vague about this [3], but in Firefox 3.0.5, for example, this "works"
across frames (I doubt it does)
function isArray(o) {
try {
Array.prototype.toString.call(o);
return true;
} catch(e) { }
return false;
}
[snip `isArray` implementation]
[2]
http://ajaxian.com/archives/isarray-why-is-it-so-bloody-hard-to-get-right#comment-270369
[3]
"The toString function is not generic; it throws a TypeError exception
if its this value is not an Array object. Therefore, it cannot be
transferred to other kinds of objects for use as a method."
[...]
> That's not good at all.
>
> I wonder if a check based on non-generic nature of
> `Array.prototype.toString` returns "proper" result. The specs are pretty
> vague about this [3], but in Firefox 3.0.5, for example, this "works"
> across frames (I doubt it does)
--------------------^
I doubt it does work with "wrapped" objects in IE.
[...]
--
kangax
> Thomas 'PointedEars' Lahn wrote:
>> Richard Cornford wrote:
> <snip>
>>> The functions above will not necessarily handle cross-frame Array
>>> detection well,
>>
>> True. If we had a reliable enough way to refer to the Array
>> object of the calling global execution context, that would
>> not be a problem.
>
> But we don't.
Are you sure?
>>> [...]
>>> Because my code is 100% driven by the spec it should be reliable
>>> (at least where implementation bugs don't enter the picture
>>
>> Unfortunately, while your approach may be written entirely
>> according to the Specification (I have not had time to confirm
>> that), it is prone to all kinds of property modification due
>> to the dynamic nature of the ECMAScript language.
>
> I would like to see that demonstrated.
>
> As far as I can see the worst outcomes would be:-
>
> 1. [...] and functions assigned to - hasOwnProperty - (possibly
> through inheritance) that give a false positive. The return value from
> the - isArray - function should still correctly report false for such an
> object.
> 2. [...]
>
> If there is something worse than that I have not noticed it, and
> would like to see it.
3. Array.prototype.concat = function() {};
4. You rely heavily on `typeof', which is implementation-dependent
for host objects.
>> ISTM that it is therefore best to use as little properties
>> as possible in such a test.
>
> It is, and I have. The - length -, - 0 - and - hasOwnProperty -
> properties are the only ones used,
And you rely on Array.prototype.concat(). Object.prototype.hasOwnProperty()
also is not available before JavaScript 1.5, JScript 5.5, ECMAScript Ed. 3,
so the test would not be backwards-compatible. Also, the method is not
always available (or working) for host objects. Facts that should be
considered for a general solution.
If you use the `constructor' property and only this property instead, the
test is backwards-compatible to JavaScript 1.1, JScript 2.0, ECMAScript Ed.
1., and is only prone to "attacks" like
var a = [];
// ...
Array.prototype.constructor = null;
// ...
isArray(a);
or
var a = {};
// ...
a.constructor = Array;
// ...
isArray(a);
(In addition it requires handling the frame problem.)
> and you only get to -
> hasOwnProperty - (which is probably the biggest modification
> vulnerability) if the object has a - 0 - property that refers to itself
> (which you have to accept is a pretty unlikely eventuality).
In a general test, no such assumptions should be made, though.
>>> [...]
>>> All the non one element arrays are identified in 8 expressions,
>>> and most non-arrays will not get past - (typeof a.length == 'number')
>>> -. The bulk of code is there to accommodate the possibility of a
>>> one element array with a reference to itself as its only element,
>>> and telling that object from a non-Array that looks like it.
>>
>> s/bulk/bloat/, sorry.
>
> The cost of trying to be general.
I think the costs can be reduced considerably while increasing reliability.
> (but the code could lose quite a few bytes in re-formatting and is
> repetitive enough to zip well)
Undoubtedly. When talking about bloated code, I'm talking about unnecessary
code, not long code.
PointedEars
> Richard Cornford wrote:
>> Object.prototype.toString.call(arrayFromAnotherWindow)
>>
>> - returns "[object Object]' in IE 6 and 7 (with similar implications for
>> the "The Miller Device" in 'isFunction' testing caused by the same
>> 'wrapping' of 'remote' objects)
>
> That's not good at all.
>
> I wonder if a check based on non-generic nature of
> `Array.prototype.toString` returns "proper" result. The specs are pretty
> vague about this [3], but in Firefox 3.0.5, for example, this "works"
> across frames (I doubt it does)
>
> function isArray(o) {
> try {
> Array.prototype.toString.call(o);
> return true;
> } catch(e) { }
^^^^^^^^^^^^^^
> return false;
^^^^^^^^^^^^^
Probably you meant to write
} catch(e) {
return false;
}
instead.
> }
That's a lot better than relying on string representations, but
unfortunately it requires at least JavaScript 1.4, JScript 5.0, ECMAScript
Ed. 3 due to try...catch, and at least JavaScript 1.3, JScript 5.5,
ECMAScript Ed. 3 due to Function.prototype.call(). So nothing I would
consider for a general test at this point. YMMV.
PointedEars
And by "extensive" you mean that it would be necessary to go through all
of (at least types of) the objects provided by a host and verify that
none of them did have "Array" as a [[Class]] property. That is quite a
difficult task even if you only attempted it on 3 or 4 'common' web
browsers.
Recall, for example, that the - item - method of IE's 'collection'
objects returns "string" from - typeof - tests and how unexpected that
is and how unlikely it was to be noticed without systematic examination
of the browser's DOM.
> [snip `isArray` tests for later review]
>
>> Object.prototype.toString.call(arrayFromAnotherWindow)
>>
>> - returns "[object Object]' in IE 6 and 7 (with similar
>> implications for the "The Miller Device" in 'isFunction'
>> testing caused by the same 'wrapping' of 'remote' objects)
>
> That's not good at all.
With cross-window scripting rapidly retreating into history I doubt that
is as much of an issues as it could have been.
> I wonder if a check based on non-generic nature of
> `Array.prototype.toString` returns "proper" result. The
> specs are pretty vague about this [3],
They are precisely as vague as step 4 in the - concat - algorithm. The
words "is not an Array object" require that the implementation has some
means of telling an Array from everything that is not an Array. Failing
to deliver that would be an implementation bug, but we have just seen an
IE implementation bug in this area.
> but in Firefox 3.0.5, for example, this "works" across frames (I doubt
> it does)
>
> function isArray(o) {
> try {
> Array.prototype.toString.call(o);
> return true;
> } catch(e) { }
> return false;
> }
<snip>
Language version questions aside (as at this point (9 years after its
release) I don't have a big problem with assuming ES 3 as a minimum), I
have never liked the idea of using try-catch for testing. This is in
part a consequence of javascript's try-catch being extremely poor and
problematic to use. You have no choice but to catch every exception that
is thrown in a - try - block, but only the exceptions that you expected
to be catching should be handled in the - catch - block. That suggests
that each - catch - block should contain the code to identify the exact
type and nature of the exception thrown so that all the exceptions that
you did not expect to be catching can be re-thrown.
In reality you almost never see this, because it is both too bulky and
too difficult. Consider, for example, verifying that the exception
thrown was the ECMAScript specified - TypeError - from the Array's -
toString - algorithm. You could try - e.name == "TypeError". Then how do
you determine that it is the TypeError thrown by the call to -
Array.prototype.toString - when the - this - object is not an array. A
TypeError has a - message - property that could say something like
"Array toString called on non-Array object", but the actual
specification says that the message is "an implementation-defined
string". That means that the string is likely vary between browsers (and
we know full well that they do vary between browsers). So while you
could examine the strings in an given set of browsers/browser
configurations you are going to hard pressed to be in a position to
identify the exception in a browser that you have never seen.
Then there is the language of the message. It is easy for US citizens
(and British and other native English speakers) to fall into the trap of
disregarding other languages. And it may be the case that javascript
engines are pretty mostly created in the US and/or do issue their error
messages in English. But there is no certainly of that, indeed it would
be entirely reasonable for a JS implementation to issues its error
messages in the language of the OS, in which case identifying exceptions
by - message - not only requires some sort of testing for the variations
that each browser may produce but also all the (200 odd possible)
language versions that may be produced by a single browser.
Unsurprisingly, you don't see people verifying that the exceptions they
have caught are the exceptions they were expecting to catch with the -
message - property (except for very 'fixed environment' code). But
ECMAScript does not offer any other error identification mechanism
beyond the broad type of the error.
One of the things that I looked at when considering my - isArray - test
was the nature of the array that gives it the most problems; the one
element array that has a reference to itself as its only elements. It is
an unlikely object but still a theoretical possibility. One of the
questions I asked myself was what would happen if its - toString -
method was called. For that object:-
1. The array's - toString - method calls its - join - method - with ','
as its argument.
2. The array's - join - method calls - ToString(el) - on the array's
single element (which is a reference to itself).
3. The internal - ToString - function (via ToPrimitive, etc) calls the
object's - toString - method, so GOTO 1.
- and we are headed for a stack overflow of some sort.
JScript and JavaScript(tm) are both smart enough not to fall into this
trap (they just return an empty string), but Opera obligingly throws a
'stack overflow' exception. Other browser may also throw exceptions (or
just overflow the stack and crash (themselves or the OS)).
This means that you cannot take an exception being thrown by -
Array.prototype.toString.call(arg) - as indicating that - arg - is not
an Array. Even without the one element array with its only element being
a reference to itself you still have to consider that - join - will
result in the - toString - methods of any object in the array being
called and any exception thrown in any of those calls (which may be a
TypeError itself) will propagate up to be caught in your try-catch block
(and that is without considering whether those - toString - method calls
have side effects (anyone writing a - toString - method that has side
effects needs their head examining (VK?))).
If you want to use try-catch for this test you really do have the
problem of verifying that any exception thrown is the exception that you
expect - Array.prototype.toString - to throw when its - this - object is
not an array, and if you do that you will "bloat" this test function by
quite a lot.
(It is unfortunate that ES 3.1's 'strict' mode will throw exceptions in
circumstances where ES 3 would not, but ES 3.1 is not going to offer any
more effective error identification mechanism, or any better try-catch
structures. The last thing the ECMAScript committee wants to be doing is
creating a 'strict' mode that is unusable/unworkable but without better
try-catch that may well be what they end up doing.)
Richard.
[snip]
> The best to date has employed what is apparently to be called the "The
> Miller Device" (after Mark Miller, who thought it up). It uses the
> specified characteristics of - Object.prototype.toString - and (in this
> context) would take the form:-
>
> function isArray ( obj ) {
> return Object.prototype.toString.call(obj) === "[object Array]";
>
> }
>
> The - Object.prototype.toString - method is specified as taking the
> internal [[Class]] property of an object and creating/returning a string
> by inserting it between '[object ' and ']'. Arrays are required to have
> a [[Class]] of "Array" and all other built-in/native objects are
> required to have other values in their [[Class]] properties.
>
> The problem with this method (and thought it is obvious I have not yet
> seen anyone point it out) is that while ECMA 262 does specify the
> precise value of [[Class]] properties for all built-in and native
> objects it does not (and could never) specify that value for host
> objects.
The under-specified host object problem is a spoiler for many things.
When checking for a host object property, a "typeof obj.prop"
expression seems to protect against host system blow-ups where "prop"
if a method and can only be called (whereas this test is just getting
the property). If a host system reports "undefined" for this test,
that doesn't indicate anything about the existence of the property as
the host is free to respond with anything it likes. It would seem
almost malicious that browser makers would return "undefined" for
typeof when a property really does exist. Safari 3 reports "undefined"
for "typeof document.all" even though document.all does exist.
> Now, step 4 in the algorithm for the - Array.prototype.concat - method
> reads:-
>
> 4. If E is not an Array object go to step 16.
>
> -, which requires ECMAScript implementations to be able to discriminate
> between an object being an Array and its not being an Array. And if the
> implementation must be able to make that discrimination then it should
> be possible to exploit that ability to make the same discrimination for
> ourselves.
Maybe not. The "is" in "is not an Array Object" is very suspicious in
the OOP sense. If a Host class subclasses Array, is an instance of
that Host class an Array or not? I can imagine ECMAScript implemented
in an OOP language like C++ or Java, using the underlying
implementation language's concept of "is" and reporting that the
instance of the Host class "is" and Array.
Peter
[...]
> And by "extensive" you mean that it would be necessary to go through all
> of (at least types of) the objects provided by a host and verify that
> none of them did have "Array" as a [[Class]] property. That is quite a
> difficult task even if you only attempted it on 3 or 4 'common' web
> browsers.
It is indeed quite difficult, but if we were to compare current ways to
test objects with `typeof` (aka `isHostMethod` and others) - aren't they
all based on the very same assumptions?
My understanding is that the reason `isHostMethod` implementation is as
it is right now is due to continuous observations and testing it in
"real life" applications. `isHostMethod` assumes that a method is
"callable" just because `typeof` returns "object", "function" or
"unknown". It doesn't actually call it (as that would be madness) -
rather, it does what we "know" to be "successful".
Isn't that an unreasonable assumption - from the purely theoretical
point of view? `isHostMethod` reliability seems to be only backed up by
an "extensive" testing. That's exactly what I was referring to regarding
[[Class]]-based `isArray`.
[..]
> With cross-window scripting rapidly retreating into history I doubt that
> is as much of an issues as it could have been.
I suppose.
>
>> I wonder if a check based on non-generic nature of
>> `Array.prototype.toString` returns "proper" result. The
>> specs are pretty vague about this [3],
>
> They are precisely as vague as step 4 in the - concat - algorithm. The
> words "is not an Array object" require that the implementation has some
> means of telling an Array from everything that is not an Array. Failing
> to deliver that would be an implementation bug, but we have just seen an
> IE implementation bug in this area.
Do you know if this has been brought up in "es-discuss" (or in other
"es-" lists)? Looking at the latest draft of ES3.1 (15jan09), I see the
statement about "an Array object" in `Array.prototype.toString` still
being identical to that from ES3.
[snip]
> This means that you cannot take an exception being thrown by -
> Array.prototype.toString.call(arg) - as indicating that - arg - is not
> an Array. Even without the one element array with its only element being
> a reference to itself you still have to consider that - join - will
> result in the - toString - methods of any object in the array being
> called and any exception thrown in any of those calls (which may be a
> TypeError itself) will propagate up to be caught in your try-catch block
> (and that is without considering whether those - toString - method calls
> have side effects (anyone writing a - toString - method that has side
> effects needs their head examining (VK?))).
>
> If you want to use try-catch for this test you really do have the
> problem of verifying that any exception thrown is the exception that you
> expect - Array.prototype.toString - to throw when its - this - object is
> not an array, and if you do that you will "bloat" this test function by
> quite a lot.
That's a great point which I completely missed.
[...]
--
kangax
This is the exact function used in the new jQuery code (along with a
similar isFunction).
I was wondering what the reasoning behind it was and whether it was
reliable. Thank for a detailed analysis.
Matt Kruse
> Now, step 4 in the algorithm for the - Array.prototype.concat - method
> reads:-
>
> 4. If E is not an Array object go to step 16.
>
> -, which requires ECMAScript implementations to be able to
> discriminate between an object being an Array and its not being an
> Array.
Or perhaps the specification is underspecified at this point.
It doesn't say what it means to "be an Array", so an implementation
could choose that you are only an Array if you are created from
the Array constructor of the current scope, but not if you are
from another frame.
So I guess this brings us full circle. To answer the question "Is this
object an Array", we must define what it means to BE an Array.
Only then can we try to detect it.
If the definition is "its [[Class]] property is 'Array'", then any
host object with that [[Class]] property IS an array, and The Miller
Device is a perfect test (until someone overwrites Object.prototype.toString).
And why do we want to detect it? Because we want to do things to it
that only works on arrays - access array properties, assume the "magic
length property", and perhaps convert it to JSON?
Generally, it's a duck test: If it walks like a duck, and it quacks
like a duck, then it's a duck. If anybody decides to create a wooden
contraption that walks as a duck and quacks like a duck, the duck test
fails, but that takes actual intent. It's not something that happens
by chance. And in a language like Javascript, you can't protect
yourself against deliberate malicious intent. Too many central properties
can be changed ("window.undefined = 42;" is allowed!)
...
> And if the implementation must be able to make that
> discrimination then it should be possible to exploit that ability to
> make the same discrimination for ourselves.
It's a clever observation. Again, you need to know whether you agree
with its definition of being an Array. And you need to know what it
is. Personally, I would accept [[Class]] == "Array" as the definition
of being an Array - it walks like a duck! At least, someone seems to
*intend* it to be an array.
However, using the concat-trick is not itself able to distinguish an
array holding itself from a non-array holding itself. In that case,
you fall back on the old and tried test of the "magic length
property", which can be fooled (having a magic length property would
be an acceptable definition of being an Array too - it quacks like a
duck!)
Now, if we are munging the suspected array object anyway, why not make
sure it's not a one-element "array".
function isArray(a) {
if (a.length === 1 && a[0] === a) {
if (a.hasOwnProperty("1")) { return false; }
a[1] = {};
if (a.length !== 2) {
delete a[1];
return false;
}
var x = [].concat(a);
a.length = 1;
if (a.hasOwnProperty("1")) {
delete a[1];
return false;
}
} else {
x = [].concat(a);
}
if (x.length == 1 && x[0] === a) { return false; }
return true;
}
> For a host object to pass this test (return true) it would have to
> have a '0' property (itself, not inherited) that referred to itself,
> a numeric length property with the value 1 and be such that
> assignments to its - length - property had Array-like side effects,
> and also that assigning to its '0' property had array-like side
> effects. A possibility but if that very unusual host object does exist
> anywhere it probably can be safely treated as an array.
Your function will probably work on an array, but on a host object,
doing "a.length = 0;" might not be safe. Heck, it might format the
hard drive!
And, anyway, in Firefox:
var x = {length: 1,
0 getter:(function() { return this.length > 0 ? this : undefined }),
0 setter:(function() { this.length = 1; })};
isArray(x); // true :P
You can't protect yourself from deliberate malicious code. I'm sure my
code can be fooled too, somehow, on some platform.
> So does this mean that, although it has no basis in ECMA 262 for
> Array/host object discrimination, the "The Miller Device" is the
> better test? Unfortunately it does not because the "The Miller Device"
> is victim of the same bug as -
>
> Object.prototype.toString.call(arrayFromAnotherWindow)
>
> - returns "[object Object]' in IE 6 and 7 (with similar implications
> for the "The Miller Device" in 'isFunction' testing caused by the same
> 'wrapping' of 'remote' objects)
So, someone intended it to not be an Array.
And the concat method agrees (probably because it tests the [[Class]]
property to see if it has an array).
/L
--
Lasse Reichstein Holst Nielsen
'Javascript frameworks is a disruptive technology'
> It's a clever observation. Again, you need to know whether you agree
> with its definition of being an Array. And you need to know what it
> is. Personally, I would accept [[Class]] == "Array" as the definition
> of being an Array - it walks like a duck! At least, someone seems to
> *intend* it to be an array.
I like duck testing but discrimination becomes difficult when synonyms
are involved.
In general, the whole type checking issue seems flawed. In Java if the
same class is loaded using two class loaders then instances of each
are not the same type. Similarly, if classes and types are added in
ECMAScript then if the same class is loaded in two windows then it
seems that instances will not be considered of the same type. This is
an understandable choice but makes writing multi-window apps with type
checking difficult (especially if a single window app grows to be
multi window.)
Peter
In many cases yes.
> My understanding is that the reason `isHostMethod` implementation
> is as it is right now is due to continuous observations and
> testing it in "real life" applications.
Real applications and dedicated examination. The result is that the
'oddities' of applying - typeof - to host objects have (possibly only
mostly) been seen. Unfortunately there are not yet decades of experience
of applying - prototype.toString.call(x) - to host objects in various
environments.
In IE 6:-
alert(
(document.forms.item)+'\n'+
(typeof document.forms.item)+'\n'+
(Object.prototype.toString.call(document.forms.item))
);
- alerts:-
[object]
string
[object String]
- which is not the sort of outcome that anyone would expect as the
result of just reasoning about what might happen here.
> `isHostMethod` assumes that a method is "callable" just
> because `typeof` returns "object", "function" or "unknown".
> It doesn't actually call it (as that would be madness) - rather, it
> does what we "know" to be "successful".
>
> Isn't that an unreasonable assumption - from the purely
> theoretical point of view?
Yes, at least the assumption that an arbitrary object is a host method
if it passes that test.
>`isHostMethod` reliability seems to be only backed up by an "extensive"
>testing.
You have to consider the intended usage for - isHostMethod -, which is
to verify whether a property that should be a host method exists in the
environment in question. So you start off with good grounds for
suspecting that the property in question is a method and are just
confirming that nothing appears to be wrong with it before using it.
Knowing where something came form can significantly constrain the
possibilities and so make types of testing that are theoretically
dubious for the general case entirely reasonable. For example, if you
knew that an object was not a host object then the "The Miller Device"
becomes a fully ECMA 262 sanctioned method of determining whether it is
a (ECMAScript native) function, array, regular expression etc.
This comes back to the question of emulating 'method overloading' in
javascript. It is the attempt to create functions/methods that are
tolerant of being passed many/unrestricted types of object as arguments
and then work out how to handle those arguments by examining the object
that introduce a need for things like - isArray - (and - isFunction -)
testing. Avoid doing that and things become much simpler and easier.
> That's exactly what I was referring to regarding [[Class]]-based
> `isArray`.
Yes, I would be surprised if there were host objects that had "Array" as
their [[Class]] properties (on the other hand, 'Function' is a different
matter), but I have been surprised by the characteristics of host
objects in the past. My experience suggests that blind optimism is
likely to be disappointed at some point.
<snip>
>>> I wonder if a check based on non-generic nature of
>>> `Array.prototype.toString` returns "proper" result. The
>>> specs are pretty vague about this [3],
>>
>> They are precisely as vague as step 4 in the - concat -
>> algorithm. The words "is not an Array object" require
>> that the implementation has some means of telling an
>> Array from everything that is not an Array. Failing to deliver that
>> would be an implementation bug, but
>> we have just seen an IE implementation bug in this area.
>
> Do you know if this has been brought up in "es-discuss"
> (or in other "es-" lists)?
I don't recall it being mentioned as such, but the 'isFunction'
(IsCallable) question has been circling around for while. There have
been attempts to clarify some of the 'is a ...' questions, including
dead ends such as trying to apply - instanceof -.
There have been suggestions of doing a future specification in the form
of ML, at which point all of these questions will need to be rendered
concrete. Not that I am entirely in favour of that as it is hard enough
to get newcomers to the subject to comprehend the spec as it is so
asking them to learn ML first seems a bit much (not everyone who comes
to browser scripting is a computer science graduate). And the
justification that people will then learn the actual language from book
would be more credible if the existing javascript books weren't
generally so very bad.
> Looking at the latest draft of ES3.1 (15jan09), I see the statement
> about "an Array object" in
> `Array.prototype.toString` still being identical to that
> from ES3.
<snip>
With the intention to finalise the new spec in the early part of this
year it is possible that there just won't be time to do anything about
sorting these questions out.
Richard.
I was surprised to see `Array.prototype.toString` being unaffected by
`Array.prototype.join`, even though specs for `Array.prototype.toString`
say that "The result of calling this function is the same as if the
built-in join method were invoked for this object with no argument." -
delete Array.prototype.join;
Array.prototype.toString.call([1,2,3]); // "1,2,3"
The result seems to be consistent in all browsers I tested (IE6+, FF3,
Safari2+, Opera 9+). Perhaps this is yet another case of
underspecification, but I would expect `join` to be late-bound in this
case. Apparently, `toString` uses some kind of internal `join` (which
`Array.prototype.join` is assigned a reference to as well)
Maybe security/integrity concerns were a reason for such implementation.
As far as try/catch approach, I can certainly see how unreliable it
could be (although some might consider this an edge case)
function isArray(o) {
try {
Array.prototype.toString.call(o);
return true;
}
catch(e) {
return false;
}
};
var err = {
toString: function() {
throw new TypeError;
}
};
var arr = [err];
isArray(arr); // false
[...]
--
kangax
Deleting Array.prototype.join should not affect "the built-in join
method".
From ECMAScript spec section 4.3.7
"A built-in object ... is present at the start of execution of an
ECMAScript program"
So the spec about Array.prototype.toString wants that method that is
there at the start of execution. It doesn't want anything you've
altered during the course of execution.
Peter
How the implementation is to determine what is or is not an Array is not
specified at all.
> It doesn't say what it means to "be an Array",
Except for what it says in section 15.4.
> so an implementation could choose that you are only an
> Array if you are created from the Array constructor of
> the current scope, but not if you are from another frame.
Could, but that does not seem a very satisfactory definition of what
being an Array is.
> So I guess this brings us full circle. To answer the question
> "Is this object an Array", we must define what it means to
> BE an Array. Only then can we try to detect it.
The advantage of exploiting step 4 in the - concat - algorithm is that
we don't have get involved in that circle. We then leave to the
implantation to work out what being an Array involves.
> If the definition is "its [[Class]] property is 'Array'",
> then any host object with that [[Class]] property IS an array,
> and The Miller Device is a perfect test (until someone overwrites
> Object.prototype.toString).
It certainly seems possible for a host to employ an ECMAScript array in
its object model, so there is a possibility that a host object is an
Array.
> And why do we want to detect it?
I don't. From my point of view this is a purely theoretical subject.
Where these sorts of things get used is in the context of emulating
method overloading in javascript. The limited effectiveness, reliability
and technical basis for these types of tests is one of the reasons for
emulating method overloading not being such a good idea in javascript.
> Because we want to do things to it that only works on arrays
> - access array properties, assume the "magic length property",
> and perhaps convert it to JSON?
Which are all things that can be subject to context specific tests.
> Generally, it's a duck test: If it walks like a duck, and it
> quacks like a duck, then it's a duck. If anybody decides to
> create a wooden contraption that walks as a duck and quacks
> like a duck, the duck test fails, but that takes actual intent.
> It's not something that happens by chance. And in a language
> like Javascript, you can't protect yourself against deliberate
> malicious intent. Too many central properties
> can be changed ("window.undefined = 42;" is allowed!)
In the end malicious intent can be directed at the test function/method
itself. If it can be accessed/used it can almost certainly have an
alternative function assigned in its place with whatever outcome the
malicious wanted.
> ...
>> And if the implementation must be able to make that
>> discrimination then it should be possible to exploit that
>> ability to make the same discrimination for ourselves.
>
> It's a clever observation. Again, you need to know whether
> you agree with its definition of being an Array.
Or at least whether the definition employed is useful.
> And you need to know what it is. Personally, I would accept
> [[Class]] == "Array" as the definition of being an Array
> - it walks like a duck! At least, someone seems to *intend*
> it to be an array.
It still is not possible to assert that by specification an object with
a [[Class]] that is "Array" is an Array. Even if it could be shown that
there were no objects in realty for which that was not true.
> However, using the concat-trick is not itself able to
> distinguish an array holding itself from a non-array holding
> itself. In that case, you fall back on the old and tried test
> of the "magic length property",
Yes, but I wanted to avoid that particular test wherever possible
because it means modifying the object being tested, even if only
temporarily.
> which can be fooled (having a magic length property would
> be an acceptable definition of being an Array too - it
> quacks like a duck!)
>
> Now, if we are munging the suspected array object anyway, why
> not make sure it's not a one-element "array".
>
> function isArray(a) {
> if (a.length === 1 && a[0] === a) {
> if (a.hasOwnProperty("1")) { return false; }
> a[1] = {};
> if (a.length !== 2) {
> delete a[1];
> return false;
> }
> var x = [].concat(a);
> a.length = 1;
> if (a.hasOwnProperty("1")) {
> delete a[1];
> return false;
> }
> } else {
> x = [].concat(a);
> }
> if (x.length == 1 && x[0] === a) { return false; }
> return true;
> }
The order of the application of the tests is not critical. I did want to
put off using -[].concat - until after verifying that the object looked
like an array (exclude all primitives and objects without numeric length
properties on those grounds alone).
>> For a host object to pass this test (return true) it would
>> have to have a '0' property (itself, not inherited) that
>> referred to itself,
>> a numeric length property with the value 1 and be such that
>> assignments to its - length - property had Array-like side
>> effects, and also that assigning to its '0' property had
>> array-like side effects. A possibility but if that very
>> unusual host object does exist
>> anywhere it probably can be safely treated as an array.
>
> Your function will probably work on an array, but on a host
> object, doing "a.length = 0;" might not be safe. Heck, it might
> format the hard drive!
Which seems like a very good reason for not attempting that until all of
the alternatives have been excused.
> And, anyway, in Firefox:
> var x = {length: 1,
> 0 getter:(function() { return this.length > 0 ? this :
> undefined }),
> 0 setter:(function() { this.length = 1; })};
> isArray(x); // true :P
>
> You can't protect yourself from deliberate malicious code. I'm
> sure my code can be fooled too, somehow, on some platform.
No, but I was only interested in ES 3 so JavaScript(tm) extensions don't
get considered. However, the ability to detect properties with getters
and setters would provide a basis for extra tests that would exclude
that object.
>> So does this mean that, although it has no basis in ECMA 262 for
>> Array/host object discrimination, the "The Miller Device" is the
>> better test? Unfortunately it does not because the "The Miller
>> Device"
>> is victim of the same bug as -
>>
>> Object.prototype.toString.call(arrayFromAnotherWindow)
>>
>> - returns "[object Object]' in IE 6 and 7 (with similar
>> implications for the "The Miller Device" in 'isFunction'
>> testing caused by the same 'wrapping' of 'remote' objects)
>
> So, someone intended it to not be an Array.
The object still has its 'magic' length property so they did not
entirely intend it not to be an array. It seems more likely that this is
cross-window effect is just an oversight (a straightforward bug).
> And the concat method agrees (probably because it tests the
> [[Class]] property to see if it has an array).
It is realistic for the people responsible for an implementation to know
with certainty that none of the host objects in their environment had a
[[Class]] of "Array", and in that case they may well conclude that it
was an adequate test for 'Arrayness'.
Richard.
Last time I needed to "check for Array" was not in context of method
overloading; It was when parsing results returned by YQL (Yahoo Query
Language)-based service which allows web document scraping (based on
given xpath, microformats, etc.)
Depending on a query I would get either:
...
"results": {
"h2": {
"content": "Account Options"
}
}
...
- or -
...
"results": {
"h1": [
{
"span": "(YHOO)",
"content": "Yahoo! Inc. \n "
},
"Yahoo! Inc."
]
}
...
When expecting this json output, there was a need to see whether
`results` property was an array - a pretty common use case to avoid
extra branching further down the road - and "cast" to it if there was
none. I chose plain `instanceof`, as it seemed like a good compromise
between simplicity/time spent vs. robustness:
...
for (var prop in data.query.results) {
if (!(data.query.results[prop] instanceof Array)) {
data.query.results[prop] = [data.query.results[prop]];
}
for (var i=0, len=data.query.results[prop].length; i<len; i++) {
conflictingElements.push(prop, data.query.results[prop][i])
}
}
...
While there was certainly a possibility to use alternative solutions,
`instanceof` appeared to convey the meaning in the most straightforward way.
--
kangax
> Lasse Reichstein Nielsen wrote:
>> Or perhaps the specification is underspecified at this point.
>
> How the implementation is to determine what is or is not an Array is
> not specified at all.
Exactly.
>> It doesn't say what it means to "be an Array",
>
> Except for what it says in section 15.4.
It says many things, but the clearest attempt at a definition is 15.4.4:
The Array prototype object is itself an array; its [[Class]] is
"Array", and it has a length property (whose initial value is +0) and
the special internal [[Put]] method described in section 15.4.5.1.
If that is taken as the definition, then it is untestable. We can't see
the internal [[Put]] method. At best we can test its behavior by setting
numeric properties or the length property, but if it's not the Array
[[Put]] method, then it might do something else to the object that we
can't undo again.
Personally, I would accept having [[Class]] Array and a numeric
(integer) length property as sufficient for being an Array.
>> so an implementation could choose that you are only an
>> Array if you are created from the Array constructor of
>> the current scope, but not if you are from another frame.
>
> Could, but that does not seem a very satisfactory definition of what
> being an Array is.
To you? I could easily accept that as well.
This is a prototype based language, so what kind of object you are is
determined by your inheritance chain. Objects from other frames have
different inheritance chains, so they are different types of objects.
Ok, it's not entirely satisfactory to me either.
> The advantage of exploiting step 4 in the - concat - algorithm is that
> we don't have get involved in that circle. We then leave to the
> implantation to work out what being an Array involves.
True. As I said: It's a neat discovery and potentially useful, if (and
only if) you find the implementations choice of Array detection
satisfactory. The implementation might actually just be testing the
[[Class]] property, and then you could just use the Miller Device
directly.
>> If the definition is "its [[Class]] property is 'Array'",
>> then any host object with that [[Class]] property IS an array,
>> and The Miller Device is a perfect test (until someone overwrites
>> Object.prototype.toString).
>
> It certainly seems possible for a host to employ an ECMAScript array
> in its object model, so there is a possibility that a host object is
> an Array.
Yes. I see no problem. If it wants to be an Array, it can be one.
If it doesn't inherit from Array.prototype, then "being an Array"
doesn't mean that you can expect the functions of an Array to be
present.
If one consider that a requirement for being an Array, then what
one is really doing is feature inference, and it would be better
to actually check for an object in the inheritance chain that
implements those functions. The "instanceof" check fails across
frames, so there is no simple way to test this.
>> And why do we want to detect it?
>
> I don't. From my point of view this is a purely theoretical subject.
Perfectly fine :)
>> It's a clever observation. Again, you need to know whether
>> you agree with its definition of being an Array.
>
> Or at least whether the definition employed is useful.
True.
>> And you need to know what it is. Personally, I would accept
>> [[Class]] == "Array" as the definition of being an Array
>> - it walks like a duck! At least, someone seems to *intend*
>> it to be an array.
>
> It still is not possible to assert that by specification an object
> with a [[Class]] that is "Array" is an Array. Even if it could be
> shown that there were no objects in realty for which that was not true.
Yes, the specification isn't definitive on what an Array is - which is
problematic since the concat algorithm must use it, so that algorithm
is underspecified.
> Yes, but I wanted to avoid that particular test wherever possible
> because it means modifying the object being tested, even if only
> temporarily.
Yes, it's a potentially destructive test. It's safe if the object
is an Array, but perhaps not if it isn't.
Probably you mean the `h1' property value as the `results' property does not
refer to an Array object in any case. But don't you see that your argument
is based on a flawed, what I would call, "jQuery thinking"?
Remember that because jQuery's overloaded $() expects anything, even
nonsense, to be passed to it, John Resig thought that this needed to be
handled within $(). Apparently it never occurred to him to date that a
function does _not_ need to handle all the values that are possible to pass
to it, not even and especially not in languages as dynamic as ECMAScript
implementations. The documentation of the function can and should inform
the user of the function what types of arguments it can accept, and if the
user passes other arguments, the function is simply not expected to work
(properly).
As for your example, the test and the casting that you think is necessary
would not have been necessary in the first place if the YQL developer(s) had
considered the issue and had always made the resulting `h1' property value
an Array object reference. Although it certainly is an inconvenience that
ECMAScript does not provide native means to determine whether a value is a
reference to an Array object, the bug is primarily in the library, not in
the language.
PointedEars