Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

An isArray test (and IE bugs)

181 views
Skip to first unread message

Richard Cornford

unread,
Jan 14, 2009, 6:48:16 PM1/14/09
to
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]";
}

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

Thomas 'PointedEars' Lahn

unread,
Jan 14, 2009, 7:02:26 PM1/14/09
to
Richard Cornford wrote:

> 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

Richard Cornford

unread,
Jan 14, 2009, 8:13:42 PM1/14/09
to
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, 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

unread,
Jan 14, 2009, 8:54:51 PM1/14/09
to
Richard Cornford wrote:

> 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

Richard Cornford

unread,
Jan 14, 2009, 9:31:47 PM1/14/09
to
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.

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

kangax

unread,
Jan 14, 2009, 10:21:15 PM1/14/09
to
Richard Cornford wrote:
> 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

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]

[1]
http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

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

kangax

unread,
Jan 14, 2009, 10:25:23 PM1/14/09
to
kangax wrote:

[...]

> 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

unread,
Jan 15, 2009, 5:48:52 AM1/15/09
to
Richard Cornford wrote:

> 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

Thomas 'PointedEars' Lahn

unread,
Jan 15, 2009, 6:04:51 AM1/15/09
to
kangax wrote:

> 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

Richard Cornford

unread,
Jan 15, 2009, 11:06:32 AM1/15/09
to
kangax wrote:
> Richard Cornford wrote:
<snip>
>> ... 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.

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.

Peter Michaux

unread,
Jan 15, 2009, 12:24:40 PM1/15/09
to
On Jan 14, 3:48 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

[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

kangax

unread,
Jan 15, 2009, 1:27:12 PM1/15/09
to
Richard Cornford wrote:

[...]

> 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

Matt Kruse

unread,
Jan 15, 2009, 1:29:46 PM1/15/09
to
On Jan 14, 5:48 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> function isArray ( obj ) {
>     return Object.prototype.toString.call(obj) === "[object Array]";
> }

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

Lasse Reichstein Nielsen

unread,
Jan 15, 2009, 4:49:50 PM1/15/09
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> writes:

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

Peter Michaux

unread,
Jan 15, 2009, 5:10:00 PM1/15/09
to
On Jan 15, 1:49 pm, Lasse Reichstein Nielsen <lrn.unr...@gmail.com>
wrote:

> 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

Richard Cornford

unread,
Jan 15, 2009, 6:42:30 PM1/15/09
to
kangax wrote:
> Richard Cornford wrote:
> [...]
>
>> 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?

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.

kangax

unread,
Jan 20, 2009, 2:25:11 AM1/20/09
to
Richard Cornford wrote:
> kangax wrote:
>> Richard Cornford wrote:
[...]

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

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

Peter Michaux

unread,
Jan 20, 2009, 6:31:01 PM1/20/09
to
On Jan 19, 11:25 pm, kangax <kan...@gmail.com> wrote:
> Richard Cornford wrote:
> > kangax wrote:
> >> Richard Cornford wrote:
> [...]
> > 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?))).
>
> 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"

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

Richard Cornford

unread,
Jan 25, 2009, 6:52:28 PM1/25/09
to
Lasse Reichstein Nielsen wrote:

> Richard Cornford writes:
>
>> 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.

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.

kangax

unread,
Jan 26, 2009, 11:31:32 PM1/26/09
to
Richard Cornford wrote:
> Lasse Reichstein Nielsen wrote:
[...]

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

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

unread,
Jan 27, 2009, 1:14:24 AM1/27/09
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> writes:

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

Thomas 'PointedEars' Lahn

unread,
Jan 28, 2009, 2:41:04 AM1/28/09
to
kangax wrote:
> Richard Cornford wrote:
>> Lasse Reichstein Nielsen wrote:
> [...]
>>> 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.
>
> 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. [...]

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

Richard Cornford

unread,
Jan 29, 2009, 9:22:53 AM1/29/09
to
<snip>

You are not recognising that this is a very different, and far
simpler, problem? You are only trying to discriminate between normal
native ECMAScript Objects and Arrays, where you can be pretty
confident about which constructor instances would have been used in
creating those object (which, of any, frames they may have come from)
and fairly sure you will not be seeing ambiguous objects.

So while it is possible to do:-

function ArrayLike(){
...
}
ArrayLike.prototype = [];
var o = new ArrayLike();

- and get a 'false' positive from - isArray = (o instanceof Array) -,
you know you will not encounter that case in the output from a JSON
parser (or eval-ing a JSON string). You will also, for example, never
encounter an array that has itself as its '0' element (JSON strings
cannot convey that structure). And looking at the JSON examples it
even seems likely that you may never encounter a non-array with a
numeric length property in this particular context.

> While there was certainly a possibility to use alternative
> solutions, `instanceof` appeared to convey the meaning in
> the most straightforward way.

In all case the test that should be performed is the test that will
tell you what you need to know. And in many contexts there will be
numerous tests that will provide the necessary answer.

However, this does illustrate a simple, if under appreciated, fact
about javascript; all code executes in a specific context. Above you
get correct behaviour with a test that is known to be unsuitable for
the general case because you are not using it in a general case, but
rather a specific context. And like all specific contexts all of the
possibilities are not present in that context, and you can see both
which are not there. The possibilities that remain become much easier
to code for in a way that is effective and reliable.

Consider your attempts earlier to read the text contents of elements.
As the problem was examined, and particular browsers, conditions and
permutations considered, the complexity of code rapidly escalated. How
much simpler does that problem become if you know up-front that the
element you are interested in is visible/displayed and has non-empty
content? Or if you know you are working on an Intranet application and
are only expected to support IE 6+ versions and Firefox 2+ versions?
And if these sorts of things were true you could know them to be true,
and so employ a version of the code that did no more than was
necessary in context.

Richard.

Richard Cornford

unread,
Jan 29, 2009, 9:23:55 AM1/29/09
to
On Jan 27, 6:14 am, Lasse Reichstein Nielsen wrote:

> Richard Cornford writes:
>> 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.

Externally untestable. Externally there is no standard means of
examining the array's [[Prototype]] (only its entire prototype chain)
and no means of accessing the entire set of possible array prototype
objects (or even Array constructors) to compare it with if there were.
The [[Class]] can be read but is not specified as being
discriminating. And the [[Put]] method is inaccessible beyond
indirectly calling it.

Internally there may be no opportunity to check that the initial value
of length, but it should be feasible for in implementation to check
that the object's [[Prototype]] property is set to any one of a set of
original Array prototypes (one for each frame/window) and that its
[[Class]] is "Array". And the implementation can examine [[Put]] to
see if it is an array [[Put]] or the standard one (as it can know
which [[Put]](s) it is using for arrays).

And if the implementation is expected to be able to make the
discrimination, as it is, it is gong to have to test something in
order to do so.

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

Yes, to me, but I am expecting implantations to be expecting to handle
multiple frames/global objects if they are expected to work with web
browsers.

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

They seem to have all covered the cross-frame issues, even if not all
have cross-window identification.

> The implementation might actually just be testing the
> [[Class]] property, and then you could just use the Miller
> Device directly.

Yes, and an implementation may be in a position to know that no other
objects will ever have a [[Class]] of "Array", in which case that test
would be valid/certain. On the other hand, the implementation may also
be checking something like the identity of the [[Put]] method in a
context where [[Put]] method code was shared between all frames/
windows. Or some other flag indicating the arrayness of some objects.

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

I would expect inheriting from - Array.prototype -(indeed having an -
Array.prototype - as its [[Prototype]] property) to be part of the
definition of being an array. Without that I would not find the
implementation's definition satisfactory. In practice that question
cannot be answered without first finding a host object that that -
concat - is treating like an array.

> If one consider that a requirement for being an Array,

The spec says the [[Prototype]] property of an array is an -
Array.prototype - so 'an Array' will inherit array method.

> then what one is really doing is feature inference,

Is that inference more than that the implementation will do what the
specification says it should?

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

If you allow for cross-window object testing then if a 'child' window
passes an object via its - opener -, but the opener had no record of
the window(s) it has opened then such a test is not even possible.

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

I disagree. The spec if very clear about what an array is, it just
does not go into how the implementation is expected to determine that
when it needs to know.

> - which is problematic since the concat algorithm must use
> it, so that algorithm is underspecified.

That would not be an easy situation to correct as the spec as it is
does not even consider the possibilities of multiple ECMAScript
environments interacting with each other.

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

Yes, so it is a good idea to restrict the objects exposed to that test
to just the ones that cannot easily be distinguished from a one
element array that has a reference to itself as its '0' property.
Those objects are quite unlikely to exist.

Richard.

kangax

unread,
Jan 29, 2009, 9:27:46 PM1/29/09
to
Thomas 'PointedEars' Lahn wrote:
> kangax wrote:
[...]

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

Yes, of course. Thanks.

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

I find overloading quite useful when used sparingly. I'm not familiar
with jQuery, but I do remember seeing some of its functions being used
in 3 (or more) different ways - depending on the type of arguments or
arguments length (perhaps it was `$`, can't remember now). I'm not a fan
of such design.

Both ES3 and DOM employ overloading to some extent. First example that
comes to mind (for ES3) is `String.prototype.replace` which supports
both Function and String objects as a second (`replaceValue`) argument.

When accessing radio button form controls through the lookup by name on
the `elements` of containing form, the result could be either a NodeList
or a single element. This looks similar to what I have described before
(and was probably a bad decision on designers' part) : )


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

Indeed. Unfortunately, we still need to "work around" it.

--
kangax

Thomas 'PointedEars' Lahn

unread,
Jan 30, 2009, 3:36:42 AM1/30/09
to
kangax wrote:
> Thomas 'PointedEars' Lahn wrote:
>> kangax wrote:
>>> 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. [...]
>> [...] 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. [...]

And, I would like to add, that one function does not need to do and should
not do all the things an API can do. What was apparently intended as an
ease of usage of the API has (predictably) caused hard to maintain code
using the API because of its ambiguity, and an unreliable API that requires
a lot of frequent checking of UA quirks on the maintainer's part. IMHO the
point of no return has passed, now that code using it is spread this far
(the MSHTML problem); while Resig could abandon $() for a couple of better
named methods, existing code using the API would have to be rewritten.

> I find overloading quite useful when used sparingly. I'm not familiar
> with jQuery, but I do remember seeing some of its functions being used
> in 3 (or more) different ways - depending on the type of arguments or
> arguments length (perhaps it was `$`, can't remember now). I'm not a fan
> of such design.

ACK.

> Both ES3 and DOM employ overloading to some extent. First example that
> comes to mind (for ES3) is `String.prototype.replace` which supports
> both Function and String objects as a second (`replaceValue`) argument.

That is the difference between a *formal specification* of *internal*
processing, and *external* testing using an *implementation* of that
specification (what you seek).

> When accessing radio button form controls through the lookup by name on
> the `elements` of containing form, the result could be either a NodeList
> or a single element. This looks similar to what I have described before
> (and was probably a bad decision on designers' part) : )

ACK, but I thought we had devised a reliable enough test for that by now.

>> 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
>
> Indeed. Unfortunately, we still need to "work around" it.

Can't you just overwrite|refactor|rewrite YQL instead?


PointedEars

Richard Cornford

unread,
Feb 2, 2009, 6:54:28 PM2/2/09
to
kangax wrote:
> Thomas 'PointedEars' Lahn wrote:
<snip>

>> 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).
>
> I find overloading quite useful when used sparingly.

Apparently not only used sparingly but also used with an appreciation of
the limitations on what can be achieved and so in circumstances where
those limitations are not significant. With the latter in place it isn't
even necessary to be sparing about the use of emulated method
overloading, though their sparing use is likely to be a consequence of
it.

> I'm not
> familiar with jQuery, but I do remember seeing some of its
> functions being used in 3 (or more) different ways - depending
> on the type of arguments or arguments length (perhaps it was `$`,
> can't remember now). I'm not a fan of such design.

As you will know, recently T.J. Crowder wrote pretty decent critique of
JQuery's method overloading:-

<URL:
http://groups.google.com/group/prototype-scriptaculous/msg/234c0431062839e6 >

- though he didn't quite go far enough in highlighting its detrimental
consequences for code maintenance. Only hinting at the consequences for
readability that follow from wrapping getters and setters up into single
overloaded methods. So if you employ an -attr - method that both gets
and sets you have to look at both the number _and_ value of the
arguments in order to determine the behaviour that will occur, but you
still have to deduce the original author's intention (as the issue is
already that the values may not be what the author intended/expected
them to be). While if you are looking at one of - getAttr - or -
setAttr - right there in the source code the original author's intention
is obvious (pretty much self-documented).

Use of the latter is easier to understand from reading the source code,
which has got to be good for maintenance (and debugging for that
matter). And if the consequence is that neither of the two methods have
to do any additional work in order to decide how they will behave and
what value they will return, and so execute quicker, then that is not
too bad either. (Especially if the logic on which they would have been
making that decision is less then sound.)

Brevity in function/method/property names may appeal to some when they
think of the work they will need to do in order to enter their code, but
it is a poor trade-off if it sacrifices that code's ability to make
assertions about its author's intentions. Particularly when you consider
that tools (and skills such as learning to touch-type[1]) can be used to
smooth the code typing process and that the code entering is something
that only has to be done once, contrasted with the fact that most
software development time/expense goes into maintenance and so the odds
are that the code will have to be read and understood many times
(including by people who did not write it).

[1] It is ironic that people champion the use of - $ - as a function
name because it is "easy to type" when for anyone who can touch-type
entering half a dozen lowercase characters is quicker/easier/less error
prone than the two handed reach of holding down Shift while hitting the
other appropriate key (the top row '4' on a UK keyboards). National
keyboard layouts probably are a factor in determining how 'convenient'
that will be.

> Both ES3 and DOM employ overloading to some extent. First example
> that comes to mind (for ES3) is `String.prototype.replace` which
> supports both Function and String objects as a second
> (`replaceValue`) argument.

The Array's - concat - method being another example.

Frequently what javascript methods/functions do is apply some of their
internal operations to the arguments that come into them. For example,
the internal - ToString - operation may get applied to an argument, and
the outcome of that is what gets acted upon. This allows for arguments
of many types but is not rally method overloading.

The - String.prototype.replace - method is a good example as it does
behave differently depending on the types of both of its arguments. And
similarly to - Array.prototype.concat -, its definition includes the
statement "If repalceValue is a function ..." without any definitive
statement of what is meant by "is a function" (native/host, callable,
has a [[Call]] method?). (I notice that the Jan 15 draft of ES 3.1 has -
String.prototype.replace - exactly as it is ES 3, so probably something
that needs to be looked at by the committee.)

> When accessing radio button form controls through the lookup by
> name on the `elements` of containing form, the result could be
> either a NodeList or a single element. This looks similar to what
> I have described before (and was probably a bad decision on
> designers' part) : )

That is not really method overloading, but rather flexibility in the
types that a property may hold (though it could be argued that the
collection's - namedItem - method is the culprit and is retuning objects
of two different types). Because they are defined in IDL the W3C's DOM
cannot express this concept, and while a 'convenience' in one sense it
reasonably could be (and has been) categorised as a bad design decision.
Though at this point it will not be going away any time soon.

>> 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
>
> Indeed. Unfortunately, we still need to "work around" it.

Richard.

0 new messages