在JavaScript中区分数组和“哈希表”

14
为了使我其中一个函数的语法更加简洁,我需要能够判断特定参数是数组还是“哈希表”(我知道它们只是对象)。
typeof无法区分它们,因为它们都返回相同的结果。 typeof {foo:"bar"} // Object typeof ["foo","bar"] // Object 那么怎样区分这两者呢?
我知道下面这个方法是行得通的,但我希望有更好的方式。 ({foo:"bar"}).constructor // Object() (["foo","bar"]).constructor // [ undefined ] 编辑:啊,看起来在火狐浏览器(firebug)中[undefined]就等同于Array。有点奇怪。
5个回答

11

你可以像SLaks建议的那样检查length属性,但是一旦将函数对象传递给它,你会感到惊讶,因为它实际上具有length属性。此外,如果对象定义了一个length属性,你会再次得到错误的结果。

你最好的选择可能是:

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

jQuery使用它,以及其他一些人... :)

这种方法比instanceof方式更加可靠。以下文章也建议使用此方法:

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

另外要补充的是,这个函数与ES 5规范中的Array.isArray函数几乎相同:

15.4.3.2 Array.isArray ( arg )

  1. 如果Type(arg)不是Object,则返回false
  2. 如果arg的[[Class]]内部属性的值为"Array",则返回true
  3. 返回false

np :) 顺便感谢 @kangax。我有点好奇 jQuery 是否因为这篇文章而改变了它的代码... :) - gblazex
对我来说,只有当我像参考文献中那样执行 Object.prototype.toString.call(obj) 时它才有效。toString.call(obj) 返回 [xpconnect wrapped native prototype] - Jamie Wong
3
这个例子来自于 jQuery,它将 Object.prototype.toString 存储在变量 toString 中,但原样使用会无效。 - bobince
@bobince +1 感谢上帝,你还活着.. :) 我总是会忘记一些东西。而“某些原因”可能是因为它更快(查找次数较少)和更短。 - gblazex
在kangax的文章评论中提到,这种方法无法在弹出窗口中使用数组,并附上以下讨论链接:https://groups.google.com/group/comp.lang.javascript/browse_frm/thread/368a55fec19af7b2/efea4aa2d12a3aa4?hl=en&lnk=gst&q=+An+isArray+test+%28and+IE+bugs%29+#efea4aa2d12a3aa4 - Tim Down
显示剩余2条评论

7

something instanceof Array 在单个文档中可以正常工作,但是如果在不同的窗口之间传递数组,则会失败,因为一个窗口中的 Array 对象与另一个窗口中的 Array 对象是不同的。如果您没有跨窗口脚本的意图(一般来说,值得避免),我建议您坚持使用这种方法。

如果您需要跨窗口支持,则情况会更加复杂。未来的故事很简单,因为 ECMAScript 第五版定义了一个函数来实现这个目的:

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

如果有此功能,您应该使用它,因为这是唯一可靠和标准认可的方法。然而,许多当今的浏览器尚不支持它。

如果没有此功能,则必须依赖 Object#toString 序列化,这样做既丑陋又有点冒险。虽然它通常可以在当今的浏览器中可靠地工作,但也有可能出现无法想象的情况(主要与宿主对象有关)。

您可以在不支持此功能的浏览器中将此回退方法添加到 Array 中,然后始终使用 Array.isArray

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

关于constructor,永远不要使用它。它不是在任何地方都可用,并且它并不会做你想象的那样。几乎总是使用它是一个错误。


3
谢谢您提醒我不要使用“constructor”,非常感谢。我已经删除了我的回答。 - user113716
这比稍微靠不住:你不需要想象任何更离奇的事情,只要从弹出窗口中获取一个数组就可能出错。请参见 https://groups.google.com/group/comp.lang.javascript/browse_frm/thread/368a55fec19af7b2/efea4aa2d12a3aa4?hl=en&lnk=gst&q=+An+isArray+test+%28and+IE+bugs%29+#efea4aa2d12a3aa4 - Tim Down

3

我认为最优雅的方法是简单地使用 instanceof 运算符:

if (myVar instanceof Array)
    doSomething();

例子:

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

编辑:

请注意,在测试来自另一个iFrame的对象时,它将失败(请参见@galambalazs和@bobince的答案)。


同意,它可以处理数组子类,不能通过各种属性伪造,并且不会使用字符串比较。 - Chadwick
如果您不需要跨窗口脚本支持,这是最干净的选择。 - bobince

1
检查 length 属性:
"length" in {foo:"bar"}   //false
"length" in ["foo","bar"] //true

1
在{foo:“bar”,length:“baz”}中的长度 // 真 - Chadwick
我不理解@Chadwick的结果。据我所知,这是正确的,但它被投票降低了。 - user113716
@Chadwick - 我明白你的意思了。我没有仔细看你的代码示例。很有道理。 - user113716
2
@Chadwick “我认为@galambalazs是指创建一个带有长度函数的类”,绝对不是。 :) 1.) 这种语言中没有类。2.) 我说的是像function f(a){}这样的函数对象,然后尝试alert(f.length)它将是一个(1)。3.) 函数实际上是这种语言中的一等对象 - gblazex
@galambalaze js对象的行为类似于类,具有基于原型的继承、构造函数,并且通过使用jQuery的.extend等方法,甚至可以测试这些“扩展”是否是“父对象”的instanceof - 但不是类。是的,函数是对象,因此您也可以将length属性分配给它们。无论Function是对象还是属性都无关紧要,我们同意 - 我只是误读了您的示例。 - Chadwick
显示剩余2条评论

0

这种类型的函数用于纠正 typeof 运算符的数组/对象冲突,以及 null/对象冲突。但它不能跨帧或跨窗口工作。请参阅源代码以获取一个更高级的版本,可以跨这些工作。

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

来源:道格拉斯·克罗克福德的JavaScript网站


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接