Cómo detectar si una variable es una matriz

101

¿Cuál es el mejor método estándar de facto entre navegadores para determinar si una variable en JavaScript es una matriz o no?

Al buscar en la web, hay una serie de sugerencias diferentes, algunas buenas y algunas no válidas.

Por ejemplo, el siguiente es un enfoque básico:

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

Sin embargo, tenga en cuenta lo que sucede si la matriz está vacía, o si obj en realidad no es una matriz, pero implementa una propiedad de longitud, etc.

Entonces, ¿qué implementación es la mejor en términos de funcionar realmente, ser entre navegadores y seguir funcionando de manera eficiente?

stpe
fuente
4
¿No volverá esto verdadero en una cadena?
James Hugard
El ejemplo dado no pretende responder la pregunta en sí, es simplemente un ejemplo de cómo se podría abordar una solución, que a menudo falla en casos especiales (como este, de ahí el "Sin embargo, tenga en cuenta ...").
stpe
@James: en la mayoría de los navegadores (excluido IE), las cadenas son similares a una matriz (es decir, es posible el acceso a través de índices numéricos)
Christoph
1
No puedo creer que esto sea tan difícil de hacer ...
Claudiu

Respuestas:

160

La verificación de tipos de objetos en JS se realiza mediante instanceof, es decir

obj instanceof Array

Esto no funcionará si el objeto pasa a través de los límites del marco, ya que cada marco tiene su propio Arrayobjeto. Puede solucionar este problema comprobando la propiedad [[Class]] interna del objeto. Para obtenerlo, utilice Object.prototype.toString()(ECMA-262 garantiza que funciona):

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

Ambos métodos solo funcionarán para matrices reales y no para objetos similares a matrices como el argumentsobjeto o las listas de nodos. Como todos los objetos tipo matriz deben tener una lengthpropiedad numérica , verificaría estos como este:

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

Tenga en cuenta que las cadenas pasarán esta verificación, lo que podría generar problemas ya que IE no permite el acceso a los caracteres de una cadena por índice. Por lo tanto, es posible que desee cambiar typeof obj !== 'undefined'a typeof obj === 'object'para excluir primitivas y objetos host con tipos distintos de todos 'object'juntos. Esto todavía permitirá que pasen los objetos de cadena, que deberían excluirse manualmente.

En la mayoría de los casos, lo que realmente desea saber es si puede iterar sobre el objeto mediante índices numéricos. Por lo tanto, podría ser una buena idea verificar si el objeto tiene una propiedad nombrada 0en su lugar, lo que se puede hacer a través de una de estas verificaciones:

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

La conversión a objeto es necesaria para que funcione correctamente para primitivas de tipo arreglo (es decir, cadenas).

Aquí está el código para comprobaciones sólidas para matrices JS:

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

y objetos similares a matrices iterables (es decir, no vacíos):

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;
    }
}
Christoph
fuente
7
Gran resumen del estado de la técnica en js array-check.
Nosredna
A partir de MS JS 5.6 (IE6?), El operador "instanceof" filtró mucha memoria cuando se ejecutó contra un objeto COM (ActiveXObject). No he comprobado JS 5.7 o JS 5.8, pero esto aún puede ser cierto.
James Hugard
1
@James: interesante, no sabía de esta filtración; de todos modos, hay una solución fácil: en IE, solo los objetos JS nativos tienen un hasOwnPropertymétodo, así que simplemente anteponga su instanceofcon obj.hasOwnProperty && ; Además, ¿sigue siendo un problema con IE7? mis pruebas simples a través del administrador de tareas sugieren que la memoria se recuperó después de minimizar el navegador ...
Christoph
@Christoph: no estoy seguro de IE7, pero IIRC no estaba en la lista de correcciones de errores para JS 5.7 o 5.8. Alojamos el motor JS subyacente en el lado del servidor en un servicio, por lo que minimizar la interfaz de usuario no es aplicable.
James Hugard
1
@TravisJ: consulte ECMA-262 5.1, sección 15.2.4.2 ; Los nombres de las clases internas están por convención en mayúsculas; consulte la sección 8.6.2
Christoph
42

La llegada de ECMAScript 5th Edition nos brinda el método de prueba más seguro si una variable es una matriz, Array.isArray () :

Array.isArray([]); // true

Si bien la respuesta aceptada aquí funcionará en marcos y ventanas para la mayoría de los navegadores, no es así para Internet Explorer 7 y versiones anteriores , ya que Object.prototype.toStringse devolverá la llamada en una matriz desde una ventana diferente [object Object], no [object Array]. IE 9 parece haber regresado a este comportamiento también (consulte la corrección actualizada a continuación).

Si desea una solución que funcione en todos los navegadores, puede usar:

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

No es del todo irrompible, pero solo alguien que se esfuerce por romperlo lo romperá. Resuelve los problemas en IE7 y versiones inferiores e IE9. El error aún existe en IE 10 PP2 , pero podría corregirse antes del lanzamiento.

PD, si no está seguro acerca de la solución, le recomiendo que la pruebe con el contenido de su corazón y / o lea la publicación del blog. Hay otras posibles soluciones allí si no se siente cómodo con la compilación condicional.

Andy E
fuente
La respuesta aceptada funciona bien en IE8 +, pero no en IE6,7
user123444555621
@ Pumbaa80: Tienes razón :-) IE8 soluciona el problema de su propio Object.prototype.toString, pero al probar una matriz creada en un modo de documento IE8 que se pasa a un modo de documento IE7 o inferior, el problema persiste. Solo había probado este escenario y no al revés. He editado para aplicar esta corrección solo a IE7 y versiones anteriores.
Andy E
WAAAAAAA, odio IE. Me olvidé por completo de los diferentes "modos de documento" o "modos esquizo", como los llamaré.
user123444555621
Si bien las ideas son geniales y se ve bien, esto no me funciona en IE9 con ventanas emergentes. Devuelve falso para las matrices creadas por el abridor ... ¿Hay alguna solución compatible con IE9? (El problema parece ser que IE9 implementa Array.isArray en sí mismo, que devuelve falso para el caso dado, cuando no debería.
Steffen Heil
@Steffen: interesante, ¿entonces la implementación nativa de isArrayno devuelve verdadero de matrices creadas en otros modos de documento? Tendré que investigar esto cuando tenga tiempo, pero creo que lo mejor que se puede hacer es enviar un error en Connect para que se pueda solucionar en IE 10.
Andy E
8

Crockford tiene dos respuestas en la página 106 de "Las partes buenas". El primero verifica el constructor, pero dará falsos negativos en diferentes marcos o ventanas. Aquí está el segundo:

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

Crockford señala que esta versión identificará la argumentsmatriz como una matriz, aunque no tenga ninguno de los métodos de matriz.

Su interesante discusión sobre el problema comienza en la página 105.

Hay más discusión interesante (post-Good Parts) aquí que incluye esta propuesta:

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

Toda la discusión me hace no querer saber si algo es una matriz o no.

Nosredna
fuente
1
esto se romperá en IE para objetos de cadena y excluirá las primitivas de cadena, que son similares a una matriz excepto en IE; comprobar [[Clase]] es mejor si desea matrices reales; si desea objetos de tipo matriz, la verificación es demasiado restrictiva
Christoph
@ Christoph: agregué un poco más a través de una edición. Tema fascinante.
Nosredna
2

jQuery implementa una función isArray, que sugiere que la mejor manera de hacerlo es

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

(fragmento tomado de jQuery v1.3.2 - ligeramente ajustado para que tenga sentido fuera de contexto)

Mario Menger
fuente
Devuelven falso en IE (# 2968). (De la fuente jquery)
razzed el
1
Ese comentario en la fuente de jQuery parece referirse a la función isFunction, no a isArray
Mario Menger
4
debería usar Object.prototype.toString()- es menos probable que se rompa
Christoph
2

Robando al gurú John Resig y jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
desgarrado
fuente
2
La segunda prueba también devolvería verdadero para una cadena: typeof "abc" .length === "number" // true
Daniel Vandersluis
2
Además, supongo que nunca debería codificar nombres de tipos, como "número". Intente compararlo con el número real, como typeof (obj) == typeof (42)
ohnoes
5
@mtod: ¿por qué no deberían codificarse los nombres de los tipos? después de todo, los valores de retorno de ¿ typeofestán estandarizados?
Christoph
1
@ohnoes
explícate
1

¿Qué vas a hacer con el valor una vez que decidas que es una matriz?

Por ejemplo, si tiene la intención de enumerar los valores contenidos si se ve como una matriz O si es un objeto que se usa como una tabla hash, entonces el siguiente código obtiene lo que desea (este código se detiene cuando la función de cierre devuelve cualquier otra cosa que "indefinido". Tenga en cuenta que NO itera sobre contenedores COM o enumeraciones; eso se deja como un ejercicio para el lector):

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

(Nota: pruebas "o! = Null" tanto para nulo como para indefinido)

Ejemplos de uso:

// 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] );
        }
    })
}
James Hugard
fuente
aunque la respuesta puede ser interesante, realmente no tiene nada que ver con la pregunta (y por cierto: iterar sobre matrices a través de for..ines malo [tm])
Christoph
@Christoph - Claro que sí. Debe haber alguna razón para decidir si algo es una matriz: porque desea hacer algo con los valores. Las cosas más típicas (en mi código, al menos) son mapear, filtrar, buscar o transformar los datos en la matriz. La función anterior hace exactamente eso: si la cosa pasada tiene elementos que se pueden iterar, entonces los itera. Si no es así, no hace nada y regresa sin definir.
James Hugard
@Christoph - ¿Por qué iterar sobre matrices con for..in bad [tm]? ¿De qué otra manera iteraría sobre matrices y / o tablas hash (objetos)?
James Hugard
1
@James: for..initera sobre propiedades enumerables de objetos; no debería usarlo con matrices porque: (1) es lento; (2) no se garantiza que mantenga el orden; (3) incluirá cualquier propiedad definida por el usuario establecida en el objeto o cualquiera de sus prototipos, ya que ES3 no incluye ninguna forma de establecer el atributo DontEnum; puede haber otros problemas que se me han olvidado
Christoph
1
@Christoph: por otro lado, usar for (;;) no funcionará correctamente para matrices dispersas y no iterará las propiedades del objeto. # 3 se considera de mala forma para Object debido a la razón que mencionas. Por otro lado, tiene TAN razón con respecto al rendimiento: for..in es ~ 36 veces más lento que for (;;) en una matriz de elementos de 1M. Guau. Desafortunadamente, no es aplicable a nuestro caso de uso principal, que es la iteración de propiedades de objeto (tablas hash).
James Hugard
1

Si está haciendo esto en CouchDB (SpiderMonkey), use

Array.isArray(array)

como array.constructor === Arrayo array instanceof Arrayno funcionan. El uso array.toString() === "[object Array]"funciona, pero parece bastante dudoso en comparación.

Daniel Worthington-Bodart
fuente
Esto funciona en Node.js y también en los navegadores, no solo en CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur
0

En w3school hay un ejemplo que debería ser bastante estándar.

Para verificar si una variable es una matriz, usan algo similar a esto

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

probado en Chrome, Firefox, Safari, ie7

Eineki
fuente
el uso constructorpara la verificación de tipos es demasiado frágil; use una de las alternativas sugeridas en su lugar
Christoph
¿Por qué piensas eso? ¿Sobre frágil?
Kamarey
@Kamarey: constructores una propiedad DontEnum regular del objeto prototipo; esto puede no ser un problema para los tipos integrados siempre que nadie haga nada estúpido, pero para los tipos definidos por el usuario puede serlo fácilmente; mi consejo: siempre use instanceof, que verifica la cadena de prototipos y no se basa en propiedades que se pueden sobrescribir arbitrariamente
Christoph
1
Gracias, encontré otra explicación aquí: thinkweb2.com/projects/prototype/…
Kamarey
Esto no es confiable, porque el objeto Array en sí mismo se puede sobrescribir con un objeto personalizado.
Josh Stodola
-2

Una de las versiones mejor investigadas y discutidas de esta función se puede encontrar en el sitio PHPJS . Puede vincular a paquetes o puede ir directamente a la función . Recomiendo encarecidamente el sitio para obtener equivalentes bien construidos de funciones PHP en JavaScript.

Tony Miller
fuente
-2

No hay suficientes referencias iguales de constructores. En algún momento tienen diferentes referencias de constructor. Entonces uso representaciones de cadenas de ellos.

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}
kuy
fuente
engañado por{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi
-4

Reemplazar Array.isArray(obj)porobj.constructor==Array

muestras:

Array('44','55').constructor==Array devuelve verdadero (IE8 / Chrome)

'55'.constructor==Array devolver falso (IE8 / Chrome)

patrick72
fuente
3
¿Por qué reemplazaría la función correcta por una horrible?
Bergi