Robust isArray() ?

V

VK

- I'm seeking the most robust and backwards-compatible
(ie, no instanceof) isArray function.

- This sentence contains two mutually exclusive demands

- No it doesn't.

- Cheking my math :) :
1) "most robust"
2) "no instanceof"
As I know from reading 14 kazillion of your posts that English is not
your primary language I can see where you might be tempted to apply some
JRS logic to the statement.

AFAIK JRS is pretty much fluent in English. He has some troubles in
expressing clearly his thoughts - but he has a grammar and a
vocabulary, which is as good as anyone could get in his remote location
(with the explainable excuse for a funny orthography). This way
bringing JRS as a sample is questionable.

:-D
It is a combined requirement, not exclusive
requirements.

First of all it is a provocative requirement. One think is to ask "How
to access controls in my form?" and another - for instance - to ask
"How to implement inheritance w/o that stupid prototype?"

The OP's question was of the second kind, so getting back not only an
actual technical answer but some theoretical explanations of his
mistakes as well should not surprise him.

For me the requirements of a kind "I want to go to the left and stay on
the same place", "I want to use native JavaScript inheritance mechanics
but without using prototype", "I want a robust way to determine
instance type but without using instanceof" - for me all these
requirements are *mutually exclusive requirements*. If you consider
them as *combined requirements* thus logically merging into one valid
achievable task, then of course it is your right to think so.

P.S. In my original post I mentioned a check based on the array
specifics. There is no way to cheat on it within the standard
JavaScript/JScript environment - no matter how experienced the
programmer is. It can be even further adjusted to accommodate Netscape
3.x as well (so bye-bye typeof operator):

function isArray(obj) {
var ret = false;
if ((obj) && (!(isNaN(parseInt(obj.length))))) {
var len = parseInt(obj.length, 10);
if (len == obj.length) {
obj[len] = 'probe';
ret = (len < obj.length);
delete obj[len];
}
}
return ret;
}

This code as absolutely robust (there is no way to cheat on it),
backward-compatible back to Netscape 2.0 and obviously goes beyond the
borders of any sanity at the current time. From the other side at the
older times c.l.j. used to solve the "practical sanity" of problems by
simply making up an imaginary browser with any features missing in any
needed combinations - so to create then imaginary problems for that
imaginary browser and then victoriously overcome them by making crazy
code chunks like the one above. (The leftovers of this approach are
still remaining in the "window size" FAQ).

So one may consider this code as my personal handful of sand on this
practice. Welcome to the 21st century:

if (someObject instanceof Array) {
...
}
 
R

Richard Cornford

VK said:
- I'm seeking the most robust and backwards-compatible
(ie, no instanceof) isArray function.

- This sentence contains two mutually exclusive demands

- No it doesn't.

- Cheking my math :) :
1) "most robust"
2) "no instanceof"
... . If you consider them as *combined requirements*
thus logically merging into one valid achievable task,
then of course it is your right to think so.

It is everyone's right to understand the language they use.
P.S. In my original post I mentioned a check based on
the array specifics.

A rather poor example as it modified all the arrays that were exposed to
it.
There is no way to cheat on it within the standard
JavaScript/JScript environment - no matter how
experienced the programmer is.

It would certainly be possible to create an object in JavaScript(tm)
that would pass this test without being an Array.
It can be even further adjusted to accommodate Netscape
3.x as well (so bye-bye typeof operator):

Rather pointless as isNaN became generally available in JavaScript 1.1,
which also introduced typeof.
function isArray(obj) {
var ret = false;
if ((obj) && (!(isNaN(parseInt(obj.length))))) {
var len = parseInt(obj.length, 10);
if (len == obj.length) {
obj[len] = 'probe';
ret = (len < obj.length);
delete obj[len];
}
}
return ret;
}

This code as absolutely robust

But a very poor test as it still modifies all arrays passed to it, and
may modify other objects it sees. A test that significantly changes the
objects it acts upon is not at all a good test. Any change would be
undesirable but adding one to the length of each and every array has got
to be an unacceptable consequence for any test of whether an object is
an array.

So one may consider this code as my personal handful of
sand on this practice. Welcome to the 21st century:

if (someObject instanceof Array) {
...
}

function AnObject(){
;
}
AnObject.prototype = [];

var obj = new AnObject();

alert((obj instanceof Array)); //alerts true
alert(isArray(obj)); //alerts false

So your - instanceof - test says this object is an array, and the -
isArray - function that above you described as reliable and impossible
to fool says that it is not an array. You may think of the 21st century
as a time for writing faulty scripts because of personal ignorance of
the behaviour of the code you are writing (and only being told that it
doesn't work a couple of times), others are free to disagree.

Richard.
 
R

Richard Cornford

VK wrote:
function isArray(obj) {
var ret = false;
if ((obj) && (!(isNaN(parseInt(obj.length))))) {
var len = parseInt(obj.length, 10);
if (len == obj.length) {
obj[len] = 'probe';
ret = (len < obj.length);
delete obj[len];
}
}
return ret;
}

This code as absolutely robust (there is no way to
cheat on it),
<snip>

So this object cannot fool your - isArray - test:-

function AnObject(){
;
}
AnObject.prototype = {
length:{
count:0,
valueOf:function(){
return this.count++;
},
toString:function(){
return '0';
}
}
};
var obj = new AnObject();

alert(('isArray(obj) = '+isArray(obj)));//alerts isArray(obj) = true;

-? (using - typeof - should seem like a better idea now as it would not
have been so easily fooled)

Richard.
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]>,
VK said the following on 12/15/2006 12:11 PM:

As I know from reading 14 kazillion of your posts that English is not
your primary language

Agreed. I have always considered him to be an American immigrant of the
first or second generation.
I can see where you might be tempted to apply some JRS logic to the
statement. It is a combined requirement, not exclusive requirements.

It is ambiguous, to those who understand how English should be written.

The "most robust and backwards-compatible" could be intended to mean
((most robust) and (backwards-compatible)) - that's a rational
requirement, if and only if (backwards-compatible) is of Boolean nature.

The use of "ie" (which should be "i.e.", standing for /id est/) in
principle implies that "no instanceof" is a necessary and sufficient
condition for code to be backwards-compatible (clearly it is necessary;
but is it sufficient? I doubt that). However, ISTM bold to presume that
the author was aware when writing of the distinction between "i.e." and
"e.g." (standing for /exempli gratia/); if "e.g." should have been used,
then "no instanceof" is not necessarily deemed a sufficient condition.

It could be intended to mean ((most robust) and (most
backwards-compatible)) - and, unless it is possible to be ((completely
robust) or (completely backwards-compatible)), that is an undecidable
condition unless there are agreed or stated scales of merit for those
two properties so that for any proposed solution a total figure of merit
can be assessed.


Of course, using "most" is only appropriate for properties measurable
(in principle) on a scale, which is hardly the case here. So "...
seeking a robust and backwards-compatible ..." must be the real intent.
 
M

Matt Kruse

Richard said:
That was my impression too, but there was no specification of what
being an array means in this context.

I clarified a bit, but I was also hoping to get some opinions about what
people think should happen with such a function.
And that question is rendered
more unclear by Matt's subsequent suggestion that - arguments -
objects should be included

My thought was that anything that behaves like an array should be considered
an array. Thinking of when the function would be used helps to determine how
it should behave.

For example: document.forms["name"].elements["firstname"]

Such a function could be used to figure out if this is a collection of
elements or a single element. Since the collection wouldn't be an instanceof
Array, yet still behaves as expected in a for() loop to iterate over the
results, I would expect isArray to return true.
 
R

Randy Webb

Matt Kruse said the following on 12/16/2006 7:51 PM:
I clarified a bit, but I was also hoping to get some opinions about what
people think should happen with such a function.

My original thought was that you wanted to test an object to see if it
was an Array or not. The only thing I noted was that you have to pass
the array itself and it failed if you gave it only the name of the array.
My thought was that anything that behaves like an array should be considered
an array.

A better name for the function would be isCollection if you just want
Objects that can be iterated over like an Array but doesn't have to
specifically be an Array.
Thinking of when the function would be used helps to determine how
it should behave.

For example: document.forms["name"].elements["firstname"]

Such a function could be used to figure out if this is a collection of
elements or a single element. Since the collection wouldn't be an instanceof
Array, yet still behaves as expected in a for() loop to iterate over the
results, I would expect isArray to return true.

isCollection would be a better name :)

I think your goal is to broad to do in a simple single test though.
There are different collections that require different tests and you
would have to test for every single possible collection/array
possibility to be able to return true on them all.
 
R

Richard Cornford

Matt said:
I clarified a bit, but I was also hoping to get some opinions
about what people think should happen with such a function.

It always has been, and remains, my opinion that a test procedure should
be determined by what you want to know from the test. That is, the
answer starts with deciding what the test function is for, and how it is
going to be used.

If you call a function - isArray - then I would expect it to reject
things that are not arrays and make a positive assertion about all
javascript arrays and anything that was indistinguishable from an array.
But the thing that most stands out as defining array-ness in javascript
is that assigning to an 'array index' property may change the - length -
property and assigning to the length property may change a number of
'array index' properties. The challenge would then be to test that
(which implies attempting modifications to the object) without leaving
the subjects of the test modified in any way. Extra tests for the
methods of an Array may allow the exclusion of many non-array objects
prior to attempting modifications.

Such a test would not categorise NodeLists, Collections or arguments
objects as arrays.
My thought was that anything that behaves like an array
should be considered an array.

And the arguments object does not behave like an Array.

Thinking about this it occurs to me that I have never verified how well
implementations handle the requirement that changing the value of an
'array index' member of the - arguments - object should have the side
effect of changing the value of corresponding named properties of the
Activation/Variable object (and vice versa), but as you can pass an
arguments object to another function there should be some interesting
cross-closure communication possibilities in that relationship if it is
reliable.
Thinking of when the function would be used
helps to determine how it should behave.

For example: document.forms["name"].elements["firstname"]

Such a function could be used to figure out if this is a
collection of elements or a single element.

I would have considered that a case where a collection specific test
would be more appropriate.
Since the collection wouldn't be an instanceof Array,
yet still behaves as expected in a for() loop to
iterate over the results,

Any object with a non-negative numeric integer length property less than
or equal to (2 to the power of 53) can be iterated over with a - for -
loop, because trying to read non-existing 'index' properties would just
return the Undefined value, as would happen with a sparse array. That is
a fairly easy test applied only to the - length - property; numeric type
and acceptable range. (All functions would pass such a test).
I would expect isArray to return true.

I would not call a function making such a test - isArray -.

Richard.
 
V

VK

Richard said:
A rather poor example as it modified all the arrays that were exposed to
it.

? What do you think is [delete] operator for? To beautify the code?
It would certainly be possible to create an object in JavaScript(tm)
that would pass this test without being an Array.

It is possible over JScript behavior or JavaScript binding (Gecko) -
but I already meantioned it in my original post. It is absolutely not
possible within the frame of ECMAScript 3rd ed.
isNaN became generally available in JavaScript 1.1,
which also introduced typeof.

Can very well be true: everything before Netscape 3.0 is getting really
foggy now in my memory.
 
R

Richard Cornford

VK said:
Richard said:
VK wrote:
A rather poor example as it modified all the arrays that
were exposed to it.

? What do you think is [delete] operator for? To beautify
the code?

What did you understand me to mean when I wrote:-

| Any test that is intended to differentiate between objects
| that are arrays and objects that are not arrays should not
| be designed to permanently alter the object that is tested.
| Here any array that passes the test will find itself with a
^^^^^^^^^^^^^^^^^^
| - length - property that is one longer than it was to start
^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| with, which is pretty important given what an array is.
^^^^
| (All javascript objects, including arrays, use the same
^^^^^^^^
| internal [[Delete]] method, and it has no side-effects).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-? Especially the words "All javascript objects, including arrays, use
the same internal [[Delete]] method, and it has no side-effects". You
have added one to the length of every array. That is the basis of the
test, but deleting the 'array index' member you have added to the array
does not have any side-effects on the array's - length - property so it
remains one greater then it was to start with.

Given that you don't understand the code you write you really should try
reading the actual wording of the corrections you are given and trying
to understand them. I should not need to state your fault three times
before you see it for yourself. (assuming you can see it now)
It is possible over JScript behavior or JavaScript binding
(Gecko) - but I already meantioned it in my original post.
It is absolutely not possible within the frame of ECMAScript
3rd ed.

Didn't you see my other reply to that test code with the standard
javascript object that fools your " absolutely robust" into believing it
is an array without its even inheriting the methods or an array? I
suppose I had better post it again for your benefit:-

function AnObject(){
;
}
AnObject.prototype = {
length:{
count:0,
valueOf:function(){
return this.count++;
},
toString:function(){
return '0';
}
}
};
var obj = new AnObject();

alert(('isArray(obj) = '+isArray(obj)));//alerts isArray(obj) = true;

That object will fool the test code you posed and it is noting but ECMA
262, 3rd Ed. conforming code.
isNaN became generally available in JavaScript 1.1,
which also introduced typeof.

Can very well be true: everything before Netscape 3.0 is
getting really foggy now in my memory.
function isArray(obj) {
var ret = false;
if ((obj) && (!(isNaN(parseInt(obj.length))))) {
var len = parseInt(obj.length, 10);
if (len == obj.length) {
obj[len] = 'probe';
ret = (len < obj.length);
delete obj[len];
}
}
return ret;
}

This code as absolutely robust

Richard.
 
J

Jeremy

Matt said:
I'm seeking the most robust and backwards-compatible (ie, no instanceof)
isArray function.

Here's what I have:

function defined(o) {
return typeof(o)!="undefined";
}
function isArray(o) {
// If these conditions aren't met, it certainly isn't an Array
if (o==null || typeof(o)!="object" || typeof(o.length)!="number") {
return false;
}
// Check to see if the object is an instance of the window's Array object
if (defined(Array) && defined(o.constructor) && o.constructor==Array) {
return true;
}
// It might be an array defined from another window object - check to see
if it has an Array's methods
if (typeof(o.join)=="function" && typeof(o.sort)=="function" &&
typeof(o.reverse)=="function") {
return true;
}
// As a last resort, let's see if index [0] is defined
return (o.length==0 || defined(o[0]));
};

Suggestions?

If by "array" you mean something that can be iterated over with the
following construct:

for(var index = 0; index < myArray.length; index++)
alert(myArray[index]);

without error, then any object that has a non-negative numeric 'length'
property will satisfy your criteria.

As for your last test:
return (o.length==0 || defined(o[0]));

Consider the following:

var x = new Array();
x[5] = "garbanzo";
alert(typeof n[0]); //"undefined" in FF 1.5

I would instead check whether o[o.length - 1] is defined - if it's not,
then o has no reason to have length of o.length (however there is
nothing preventing you from explicitly setting the length of the array).

In the above example:

var x = new Array();
x[5] = "garbanzo";
alert(typeof n[n.length - 1]); //"string"

Jeremy
 
M

Matt Kruse

Jeremy said:
If by "array" you mean something that can be iterated over with the
following construct:
for(var index = 0; index < myArray.length; index++)
alert(myArray[index]);
without error, then any object that has a non-negative numeric
'length' property will satisfy your criteria.

I suppose, but that doesn't mean that numeric indexes from 0..(length-1) of
the object are intended to hold any meaningful data.
As for your last test:
return (o.length==0 || defined(o[0]));
Consider the following:
var x = new Array();
x[5] = "garbanzo";
alert(typeof n[0]); //"undefined" in FF 1.5

That's why this case would already be trapped by the code about the final
test.
I would instead check whether o[o.length - 1] is defined - if it's
not, then o has no reason to have length of o.length

This might be logically true, but one could easily do:

var empty = [,,,,,];
 
V

VK

Richard said:
That is the basis of the
test, but deleting the 'array index' member you have added to the array
does not have any side-effects on the array's - length - property so it
remains one greater then it was to start with.

Hum... indeed. That's kind of a silly behavior I did not notice before
- as I never used delete on array elements. IMO delete should work the
same way as delete someObject.someProperty - not keeping someProperty
with undefined value but _delete_ it as it never existed.
Well, it's easy to fix:

function isArray(obj) {
var ret = false;
var len = 0;

if (obj) {
len = parseInt(obj.length, 10);
if (len == len) {
obj[len] = 'probe';
ret = (len < obj.length);
obj.length = len;
}
}

return ret;
}

Didn't you see my other reply to that test code with the standard
javascript object that fools your " absolutely robust" into believing it
is an array without its even inheriting the methods or an array? I
suppose I had better post it again for your benefit:-

function AnObject(){
;
}
AnObject.prototype = {
length:{
count:0,
valueOf:function(){
return this.count++;
},
toString:function(){
return '0';
}
}
};
var obj = new AnObject();

alert(('isArray(obj) = '+isArray(obj)));//alerts isArray(obj) = true;

What browser did you test it on? Of course it fails (false). As I said,
there is no technical way to implement compound property within
ECMAScript 3rd ed. It's not a teasing for an intellectual challenge,
just a constatation of a fact. Respectively this isArray is absolutely
rubust yet absolutely not needed with instanceof alive.
 
R

Richard Cornford

VK said:
Hum... indeed. That's kind of a silly behavior I did not
notice before - as I never used delete on array elements.

And you only needed to be told why your original - isArray - function
was inadequate three times before you tried it for yourself.
IMO delete should work the same way as delete
someObject.someProperty - not keeping someProperty
with undefined value but _delete_ it as it never existed.

That is exactly what - delete - does do. I don't know what exactly
passes as testing with you but it doesn't appear to leave yo0u any
better informed.
Well, it's easy to fix:

function isArray(obj) {
var ret = false;
var len = 0;

if (obj) {
len = parseInt(obj.length, 10);
if (len == len) {
obj[len] = 'probe';
ret = (len < obj.length);
obj.length = len;
}
}

return ret;
}

You really don't understand what the code you write does, and even when
your faults are pointed out you just don't know how to correct them. You
have change code that modifies the - length - property of all the arrays
that are exposed to it into code that adds a new (or changes an
existing) property to (on) every non-array object it encounters that has
a numeric - length - property (which, at minimum, includes all String
and Function objects, plus many custom objects). A test function really
should not permanently modify the objects that it tests.
What browser did you test it on?

Firefox, Opera and IE (though it is pure ECMAScript so would be expected
to work with all anyway). Why didn't you test it? Or why couldn't you
manage to perform such a simple test effectively?
Of course it fails (false).

When the - isArray - function is the function that you posted and
declared "absolutely robust", that was:-

| function isArray(obj) {
| var ret = false;
| if ((obj) && (!(isNaN(parseInt(obj.length))))) {
| var len = parseInt(obj.length, 10);
| if (len == obj.length) {
| obj[len] = 'probe';
| ret = (len < obj.length);
| delete obj[len];
| }
| }
| return ret;
| }

- the result is exactly as I described; the alert is 'isArray(obj) =
true'. You could verify that by trying it yourself, as anyone else can.
As I said, there is no technical way to implement
compound property within ECMAScript 3rd ed.

As your whole "compound property" thing was a fiction with no reality
outside your own head anything you say on the subject is irrelevant.
It's not a teasing for an intellectual challenge,
just a constatation of a fact.

It is a fiction.
Respectively this isArray

"This isArray"? Not the previous one?
is absolutely rubust

So there can be no object for which - isArray(obj) - (where - isArray -
is your new version rather than the previous version that reported -
true - for the object I already posted) returns true?

One of the consequences of your basing your conception of javascript on
your ludicrous fictions is that you end up making statements that are
easily demonstrated as false. This object:-

function AnObject(){
;
}
AnObject.prototype = {
length:{
valueOf:function(){
return 1;
},
toString:function(){
return '0';
}
}
};
var obj = new AnObject();

alert(('isArray(obj) = '+isArray(obj)));//alerts isArray(obj) = true

- returns - true - from your newest "absolutely robust" array test, even
though it is clearly not an array. It does so for precisely the same
reason as my previous object returned true from your previous -
isArray - test. And if you had bothered to test my previous object with
your previous test you may have seen the obvious truth that would have
suggested that you not label this test as any more "absolutely robust"
as your last easily fooled test.
yet absolutely not needed with instanceof alive.

If you had understood my criticism of your use of - instanceof - in this
context you would be able to see why that would not help anyway.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top