tags:

views:

130

answers:

5

In order to make the syntax for one of my functions nicer, I need to be able to tell whether a specific parameter is an array or "hash" (which I know are just objects).

Typeof doesn't work, because they both return the same thing

typeof {foo:"bar"} // Object

typeof ["foo","bar"] // Object

So how would I differentiate between the two?

I know this works, but I'm hoping there's a nicer way

({foo:"bar"}).constructor // Object()

(["foo","bar"]).constructor // [ undefined ]

EDIT Ah, it seems [ undefined ] in firebug is the same thing as Array. Kind of weird.

A: 

Check for the length property:

"length" in {foo:"bar"}   //false
"length" in ["foo","bar"] //true
SLaks
`"length in {foo:"bar", length:"baz"}` // true`
Chadwick
-1 unfortunately function objects will ruin this, see my answer... + what Chadwick said
galambalazs
I don't get @Chadwick's result. Far as I can tell, this is correct, yet it is down voted.
patrick dw
A hash (object) can have a `length` property as seen in my comment above. I think @galambalazs is referring to creating a class with a `length` function, which would be equivalent. Just because an object has a `length` property, does not mean it's an Array.
Chadwick
@Chadwick - I see what you mean. I didn't look closely enough at your code example. Makes sense.
patrick dw
@Chadwick `"I think @galambalazs is referring to creating a class with a length function,"` - definetely not. :) *1.)* There are no classes in the language. *2.)* I said a **function object** like `function f(a){}` then try `alert(f.length)` it will be one(1). *3.)* **functions** are in fact **first-class** objects in this language
galambalazs
@galambalaze js objects behave close to Classes, with prototype based inheritance, constructors, and by using things like jQuery's `.extend` one can even test if those 'extensions' are `instanceof` the 'parent object' - but no, they are not Classes. Yep, Functions are Objects, so you could assign a `length` property to them as well. Whether the `Function` is the object or the property is irrelevant, we agree - I merely misread your example.
Chadwick
A: 

This typeof function is used to correct the array/object clash for the typeof operator, and the null/object clash. It does not however work across frames or across windows. See the source for a more advanced version that works across these.

function typeOf(value) {
    var s = typeof value;
    if (s === 'object') {
        if (value) {
            if (value instanceof Array) {
                s = 'array';
            }
        } else {
            s = 'null';
        }
    }
    return s;
}

Source: Douglas Crockford's Javascript site

Macha
This received a down vote too. Looks correct to me.
patrick dw
+5  A: 

You could check the length property as SLaks suggested, but as soon as you pass it a function object you'll be surprised, because it in fact has a length property. Also if the object has a length property defined, you'll get wrong result again.

Your best bet is probably:

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

jQuery uses it, and a "couple of" other people... :)

It is more fail proof than the instanceof way. The method is also suggested by the following article:

'instanceof' considered harmful (or how to write a robust 'isArray') (@kagax)

Another thing to add that this function is almost identical to the Array.isArray function in ES 5 spec:

15.4.3.2 Array.isArray ( arg )

  1. If Type(arg) is not Object, return false.
  2. If the value of the [[Class]] internal property of arg is "Array", then return true.
  3. Return false.
galambalazs
Good article showing how instanceof may fail when using iframes.
Chadwick
Lots of good answers, but this looks like the best way. Thanks for the reference too.
Jamie Wong
np :) thank to @kangax btw. I'm kinda curious if jQuery changed it's code to this method because of the article though... :)
galambalazs
For me, it only works if I do `Object.prototype.toString.call(obj)`, as in the reference. toString.call(obj) return `[xpconnect wrapped native prototype]`
Jamie Wong
The example is nicked from jQuery, which puts `Object.prototype.toString` in the variable `toString` for some reason. It won't work as-is.
bobince
@bobince +1 thank God, you live.. :) I always forget stuff. And "some reason" is probably that it's faster (less lookup) and shorter.
galambalazs
+2  A: 

I think the most elegant way is to simply use the instanceof operator:

if (myVar instanceof Array)
    doSomething();

examples:

[] instanceof Array           // true
{} instanceof Array           // false
{length:100} instanceof Array // false
null instanceof Array         // false

Edit:

Be aware that it will fail when testing an object from another iFrame (see answers by @galambalazs and @bobince)

Chadwick
This received a down vote too. Looks correct to me.
patrick dw
Agreed, it handles array subclasses, can't be faked with various properties, and doesn't resort to string comparisons.
Chadwick
This is cleanest if you don't need cross-window scripting support.
bobince
+3  A: 

something instanceof Array works fine within a single document, but will fail if you start passing arrays between different windows, because the Array from one window is a different object from Array on another. If you have no intention of doing cross-window-scripting (and in general, it's worth avoiding) I would recommend sticking with this.

If you need cross-window support, things are a bit more complex. In the future the story is simple, as ECMAScript Fifth Edition defines a function to do exactly this:

Array.isArray([1]);   // -> true

You should use this functionality where available as it's the only reliable and standards-endorsed way. However, many of today's browsers don't support it yet.

Where it isn't available you have to rely on Object#toString serialisation, which is ugly and slightly dodgy. Although it will in general work reliably with today's browsers, there are imaginable cases where it might not (primarily to do with host objects).

You can hack this fallback method into Array on browsers that don't support it, and then use Array.isArray at all times:

if (!('isArray' in Array)) {
    Array.isArray= function(o) {
        return Object.prototype.toString.call(o)==='[object Array]';
    };
}

As for constructor, never use it. It's not available everywhere and it doesn't do what you think. Using it is almost always a mistake.

bobince
bobince: +1 *Thank you* for the note on not using `constructor`. Much appreciated. I deleted my answer.
patrick dw