如何检测变量是否为数组


101

确定JavaScript中的变量是否为数组的最佳事实标准跨浏览器方法是什么?

在网络上搜索时,有很多不同的建议,有些是好的,有些是无效的。

例如,以下是一种基本方法:

function isArray(obj) {
    return (obj && obj.length);
}

但是,请注意,如果数组为空,或者obj实际上不是数组而是实现了length属性等,会发生什么情况。

那么,从实际工作,跨浏览器和仍有效执行的角度来看,哪种实现是最佳的呢?


4
这不会在字符串上返回true吗?
James Hugard 09年

给出的示例并非要回答问题本身,而仅仅是解决方案的示例-在特殊情况下(如这种情况,因此常常失败,因此出现“但是,请注意...”),该解决方案通常会失败。
stpe

@James:在大多数浏览器中(不包括IE),字符串都类似于数组(即可以通过数字索引进行访问)
Christoph

1
不能'相信这样做是如此困难...
克劳迪乌(Claudiu)2009年

Answers:


160

JS中对象的类型检查是通过完成的instanceof,即

obj instanceof Array

如果对象跨帧边界传递,则将不起作用,因为每个帧都有自己的Array对象。您可以通过检查对象的内部[[Class]]属性来解决此问题。要获得它,请使用Object.prototype.toString()(ECMA-262保证可以使用):

Object.prototype.toString.call(obj) === '[object Array]'

这两种方法仅适用于实际的数组,不适用于arguments对象或节点列表之类的数组对象。由于所有类似数组的对象都必须具有数字length属性,因此我将检查以下内容:

typeof obj !== 'undefined' && obj !== null && typeof obj.length === 'number'

请注意,字符串将通过此检查,这可能会导致问题,因为IE不允许按索引访问字符串的字符。因此,您可能需要更改typeof obj !== 'undefined'typeof obj === 'object'排除原始类型和宿主对象,这些对象和宿主对象的类型不同'object'。这仍将让字符串对象通过,必须手动将其排除。

在大多数情况下,您真正​​想知道的是是否可以通过数字索引遍历对象。因此,最好检查对象是否具有名为的属性0,这可以通过以下检查之一来完成:

typeof obj[0] !== 'undefined' // false negative for `obj[0] = undefined`
obj.hasOwnProperty('0') // exclude array-likes with inherited entries
'0' in Object(obj) // include array-likes with inherited entries

对于类似数组的基元(即字符串),强制转换为对象是正常工作所必需的。

这是对JS数组进行健壮检查的代码:

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

和可迭代(即非空)的类似数组的对象:

function isNonEmptyArrayLike(obj) {
    try { // don't bother with `typeof` - just access `length` and `catch`
        return obj.length > 0 && '0' in Object(obj);
    }
    catch(e) {
        return false;
    }
}

7
js数组检查技术的最新摘要。
Nosredna

从MS JS 5.6(IE6?)开始,针对COM对象(ActiveXObject)运行时,“ instanceof”运算符泄漏了很多内存。尚未检查JS 5.7或JS 5.8,但这可能仍然适用。
James Hugard 09年

1
@James:很有意思-我不知道这次泄漏;无论如何,有一个简单的解决方法:在IE中,只有本机JS对象才具有hasOwnProperty方法,因此只需在您的前面instanceof加上obj.hasOwnProperty && ;即可。另外,这仍然是IE7的问题吗?我通过任务管理器进行的简单测试表明,在最小化浏览器之后,内存已被回收...
Christoph

@Christoph-不确定IE7,但是IIRC不在JS 5.7或5.8的错误修复列表中。我们将底层JS引擎托管在服务中的服务器端,因此最小化UI是不适用的。
James Hugard 09年

1
@TravisJ:参见ECMA-262 5.1,第15.2.4.2节;内部类的名字是按照惯例大写-参见8.6.2节
克里斯托夫

42

ECMAScript 5th Edition的到来为我们提供了测试变量是否为数组的最可靠方法Array.isArray()

Array.isArray([]); // true

尽管此处接受的答案对大多数浏览器而言适用于所有框架和窗口,但不适用于Internet Explorer 7及更低版本,因为Object.prototype.toString从其他窗口调用数组将返回[object Object],而不是[object Array]。IE 9似乎也已退回到此行为(请参阅下面的更新修复)。

如果您想要一个适用于所有浏览器的解决方案,则可以使用:

(function () {
    var toString = Object.prototype.toString,
        strArray = Array.toString(),
        jscript  = /*@cc_on @_jscript_version @*/ +0;

    // jscript will be 0 for browsers other than IE
    if (!jscript) {
        Array.isArray = Array.isArray || function (obj) {
            return toString.call(obj) == "[object Array]";
        }
    }
    else {
        Array.isArray = function (obj) {
            return "constructor" in obj && String(obj.constructor) == strArray;
        }
    }
})();

它不是完全坚不可摧的,但是只有被努力打破它的人才能打破它。它可以解决IE7及更低版本和IE9中的问题。 该错误仍存在于IE 10 PP2中,但可能在发布前已得到修复。

PS,如果您不确定解决方案,那么我建议您对它的内容进行测试和/或阅读博客文章。如果您不满意使用条件编译,那么这里还有其他潜在的解决方案。


接受的答案在IE8 +中确实可以正常工作,但在IE6,7中则不能正常工作
user123444555621 2010年

@ Pumbaa80:您是对的:-) IE8为其自身的Object.prototype.toString修复了该问题,但是测试以IE8文档模式创建的数组并传递给IE7或更低版​​本的文档模式,问题仍然存在。我只测试了这种情况,而没有进行其他测试。我已编辑为仅将此修补程序应用于IE7及更低版本。
Andy E

WAAAAAAA,我讨厌IE。我完全忘记了不同的“文档模式”或“ schizo模式”,我将其称为“文件模式”。
user123444555621 2010年

尽管这些想法很棒,而且看起来不错,但在带有弹出窗口的IE9中,这对我不起作用。对于由打开器创建的数组,它返回false。是否有IE9兼容解决方案?(这个问题似乎是,IE9 implemnets Array.isArray本身,这对于给定的情况下返回false,当它不应该。
斯特芬·埃尔

@Steffen:有趣,因此的本机实现isArray是否不会以其他文档模式下创建的数组返回true?我得看看这个,当我得到一些时间,但我觉得做的最好的事情是文件上连接一个bug,因此它可以固定在IE 10
安迪·ê

8

克罗克福德在“好零件”的第106页上有两个答案。第一个检查构造函数,但是会在不同的框架或窗口中给出假阴性。这是第二个:

if (my_value && typeof my_value === 'object' &&
        typeof my_value.length === 'number' &&
        !(my_value.propertyIsEnumerable('length')) {
    // my_value is truly an array!
}

Crockford指出,即使该版本arguments没有任何数组方法,该版本仍会将数组标识为数组。

他对问题的有趣讨论从第105页开始。

这里还有一个更有趣的讨论(后零件)其中包括以下建议:

var isArray = function (o) {
    return (o instanceof Array) ||
        (Object.prototype.toString.apply(o) === '[object Array]');
};

所有的讨论都使我永远不想知道某个东西是否是数组。


1
这将在IE中破坏字符串对象,并排除字符串基元,除了IE中,它们类似于数组。如果需要实际数组,请检查[[Class]]更好;如果您想要类似数组的对象,则检查在imo上过于严格
Christoph

@ Christoph-我通过编辑添加了更多内容。有趣的话题。
Nosredna,2009年

2

jQuery实现了isArray函数,这建议最好的方法是

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

(摘自jQuery v1.3.2的代码段-略有调整,以脱离上下文)


他们在IE(#2968)上返回false。(从jquery的源)
razzed

1
jQuery源中的注释似乎是指isFunction函数,而不是isArray
Mario Menger

4
你应该使用Object.prototype.toString()-这是不太可能打破
克里斯托夫

2

窃取大师John Resig和jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}

2
第二个测试也将对字符串返回true:typeof“ abc” .length ===“ number” // true
Daniel Vandersluis,2009年

2
另外,我猜您永远都不要对类型名称(例如“数字”)进行硬编码。尝试将其与实际数字进行比较,例如typeof(obj)== typeof(42)
ohnoes 2009年

5
@mtod:为什么不应该对名称进行硬编码?毕竟,的返回值typeof是标准化的吗?
Christoph

1
@ohnoes解释自己
Pacerier,2011年

1

一旦确定它是一个数组,您将如何处理该值?

例如,如果您打算枚举包含的值(如果它看起来像一个数组)或者它是一个用作哈希表的对象,那么下面的代码将获得所需的代码(当闭包函数返回任何其他值时,此代码停止请注意,它不会遍历COM容器或枚举;留给读者练习):

function iteratei( o, closure )
{
    if( o != null && o.hasOwnProperty )
    {
        for( var ix in seq )
        {
            var ret = closure.call( this, ix, o[ix] );
            if( undefined !== ret )
                return ret;
        }
    }
    return undefined;
}

(注意:“ o!= null”将同时测试null和undefined)

使用示例:

// Find first element who's value equals "what" in an array
var b = iteratei( ["who", "what", "when" "where"],
    function( ix, v )
    {
        return v == "what" ? true : undefined;
    });

// Iterate over only this objects' properties, not the prototypes'
function iterateiOwnProperties( o, closure )
{
    return iteratei( o, function(ix,v)
    {
        if( o.hasOwnProperty(ix) )
        {
            return closure.call( this, ix, o[ix] );
        }
    })
}

尽管答案可能很有趣,但实际上与问题没有任何关系(顺便说一句:遍历数组for..in很不好[tm])
Christoph

@Christoph-当然可以。确定某个对象是否为数组必须有一定的理由:因为您想对这些值进行某些处理。最典型的事情(至少在我的代码中)是映射,过滤,搜索或以其他方式转换数组中的数据。上面的函数正是这样做的:如果传递的事物具有可以迭代的元素,则对其进行迭代。如果不是,则不执行任何操作,并且无害地返回undefined。
James Hugard 09年

@Christoph-为什么要在bad [tm]中使用for ..遍历数组?您还将如何遍历数组和/或哈希表(对象)?
James Hugard 09年

1
@James:for..in遍历对象的可枚举属性;您不应该将其与数组一起使用,因为:(1)它很慢;(2)不保证保留订单;(3)由于ES3不包含任何设置DontEnum属性的方法,因此它将包含对象中任何用户定义的属性集或其任何原型;可能还有其他一些问题让我忘了
Christoph

1
@Christoph-另一方面,对于稀疏数组,使用for(;;)不能正常工作,并且不会迭代对象属性。由于您提到的原因,#3被认为是Object的不良形式。另一方面,在性能方面您是正确的:for..in比1M元素数组上的for(;;)慢36倍。哇。不幸的是,不适用于我们的主要用例,即迭代对象属性(哈希表)。
James Hugard 09年



0

w3school上,有一个例子应该很标准。

要检查变量是否为数组,他们使用类似于此的内容

function arrayCheck(obj) { 
    return obj && (obj.constructor==Array);
}

在Chrome,Firefox,Safari和ie7上进行了测试


使用constructor进行类型检查是IMO太脆; 使用建议的替代方法之一,而不是
克里斯托夫

你为什么这么认为?关于脆?
卡马瑞2009年

@Kamarey:constructor是原型对象的常规DontEnum属性;只要没有人做任何愚蠢的事情,对于内置类型来说这可能不是问题,但是对于用户定义的类型,这很容易做到;我的建议:始终使用instanceof,它检查原型链,并且不依赖于可以任意覆盖的属性
Christoph

1
谢谢,在这里找到了另一个解释:thinkweb2.com/projects/prototype/…–
Kamarey

这是不可靠的,因为Array对象本身可以被自定义对象覆盖。
乔什·斯托多拉

-2

PHPJS站点上可以找到对此功能进行研究和讨论最多的版本之一。您可以链接到软件包,也可以直接转到功能。我强烈建议该网站提供JavaScript中精心构造的PHP函数的等效项。


-2

没有足够的参考等于构造函数。有时他们有不同的构造函数引用。因此,我使用它们的字符串表示形式。

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}

{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi

-4

替换Array.isArray(obj)obj.constructor==Array

样品:

Array('44','55').constructor==Array 返回true(IE8 / Chrome)

'55'.constructor==Array 返回假(IE8 / Chrome)


3
为什么要用一个可怕的功能代替正确的功能?
Bergi 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.