Como detectar se uma variável é um array

101

Qual é o melhor método cruzado padrão de fato para determinar se uma variável em JavaScript é um array ou não?

Pesquisando na web há várias sugestões diferentes, algumas boas e outras inválidas.

Por exemplo, o seguinte é uma abordagem básica:

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

No entanto, observe o que acontece se a matriz estiver vazia, ou obj realmente não for uma matriz, mas implementar uma propriedade de comprimento, etc.

Então, qual implementação é a melhor em termos de realmente funcionar, ser cross-browser e ainda ter um desempenho eficiente?

stpe
fonte
4
Isso não retornará verdadeiro em uma string?
James Hugard
O exemplo dado não visa responder à pergunta em si, é apenas um exemplo de como uma solução pode ser abordada - que muitas vezes falha em casos especiais (como este, daí o "No entanto, note ...").
stpe
@James: na maioria dos navegadores (excluindo IE), as strings são semelhantes a array (ou seja, o acesso por meio de índices numéricos é possível)
Christoph
1
não posso acreditar que isso seja tão difícil de fazer ...
Claudiu

Respostas:

160

A verificação de tipo de objetos em JS é feita via instanceof, ou seja,

obj instanceof Array

Isso não funcionará se o objeto for passado pelos limites do quadro, pois cada quadro tem seu próprio Arrayobjeto. Você pode contornar isso verificando a propriedade interna [[Class]] do objeto. Para obtê-lo, use Object.prototype.toString()(é garantido que funcione pela ECMA-262):

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

Ambos os métodos funcionarão apenas para arrays reais e não para objetos do tipo array, como o argumentsobjeto ou listas de nós. Como todos os objetos em forma de matriz devem ter uma lengthpropriedade numérica , eu verificaria assim:

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

Observe que as strings passarão nesta verificação, o que pode causar problemas, pois o IE não permite acesso aos caracteres de uma string por índice. Portanto, você pode querer alterar typeof obj !== 'undefined'para typeof obj === 'object'para excluir primitivos e objetos de host com tipos diferentes de todos 'object'juntos. Isso ainda permitirá a passagem de objetos de string, que teriam que ser excluídos manualmente.

Na maioria dos casos, o que você realmente deseja saber é se pode iterar sobre o objeto por meio de índices numéricos. Portanto, pode ser uma boa ideia verificar se o objeto tem uma propriedade chamada 0, o que pode ser feito por meio de uma destas verificações:

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

A conversão para o objeto é necessária para funcionar corretamente para primitivos do tipo array (ou seja, strings).

Este é o código para verificações robustas para matrizes JS:

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

e objetos semelhantes a array iteráveis ​​(ou seja, não vazios):

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
fonte
7
Excelente resumo do estado da arte em verificação de array js.
Nosredna
A partir do MS JS 5.6 (IE6?), O operador "instanceof" perdia muita memória quando executado em um objeto COM (ActiveXObject). Não verifiquei JS 5.7 ou JS 5.8, mas isso ainda pode ser verdade.
James Hugard
1
@James: interessante - eu não sabia desse vazamento; de qualquer maneira, há uma solução fácil: no IE, apenas objetos JS nativos têm um hasOwnPropertymétodo, então apenas prefixe seu instanceofcom obj.hasOwnProperty && ; Além disso, isso ainda é um problema com o IE7? meus testes simples via gerenciador de tarefas sugerem que a memória foi recuperada após minimizar o navegador ...
Christoph
@Christoph - Não tenho certeza sobre o IE7, mas IIRC isso não estava na lista de correções de bugs para JS 5.7 ou 5.8. Hospedamos o mecanismo JS subjacente no lado do servidor em um serviço, portanto, minimizar a IU não é aplicável.
James Hugard
1
@TravisJ: consulte ECMA-262 5.1, seção 15.2.4.2 ; nomes de classes internas são por convenção em maiúsculas - consulte a seção 8.6.2
Christoph
42

A chegada do ECMAScript 5th Edition nos dá o método mais seguro de testar se uma variável é uma matriz, Array.isArray () :

Array.isArray([]); // true

Embora a resposta aceita aqui funcione em frames e janelas para a maioria dos navegadores, não funciona para o Internet Explorer 7 e inferior , porque Object.prototype.toStringuma matriz chamada de uma janela diferente retornará [object Object], não [object Array]. O IE 9 também parece ter regredido a esse comportamento (consulte a correção atualizada abaixo).

Se quiser uma solução que funcione em todos os navegadores, você pode 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;
        }
    }
})();

Não é totalmente inquebrável, mas só seria quebrado por alguém que tentasse quebrá-lo. Ele contorna os problemas do IE7 e inferiores e do IE9. O bug ainda existe no IE 10 PP2 , mas pode ser corrigido antes do lançamento.

PS, se você não tiver certeza sobre a solução, então eu recomendo que você teste o que quiser e / ou leia a postagem do blog. Existem outras soluções possíveis se você não se sentir confortável com a compilação condicional.

Andy E
fonte
A resposta aceita funciona bem no IE8 +, mas não no IE6,7
usuário123444555621
@ Pumbaa80: Você está certo :-) O IE8 corrige o problema para seu próprio Object.prototype.toString, mas testando um array criado em um modo de documento do IE8 que é passado para um IE7 ou modo de documento inferior, o problema persiste. Eu tinha apenas testado este cenário e não o contrário. Eu editei para aplicar essa correção apenas ao IE7 e inferior.
Andy E
WAAAAAAA, odeio o IE. Esqueci totalmente os diferentes "modos de documento", ou "modos esquizo", como vou chamá-los.
user123444555621
Embora as ideias sejam ótimas e pareçam boas, isso não funciona para mim no IE9 com janelas pop-up. Ele retorna falso para matrizes criadas pelo abridor ... Existe uma solução compatível com o IE9? (O problema parece ser que o IE9 implanta o próprio Array.isArray, que retorna falso para o caso dado, quando não deveria.
Steffen Heil
@Steffen: interessante, então a implementação nativa de isArraynão retorna verdadeiro de arrays criados em outros modos de documento? Terei de averiguar isso quando tiver algum tempo, mas acho que a melhor coisa a fazer é registrar um bug no Connect para que possa ser corrigido no IE 10.
Andy E
8

Crockford tem duas respostas na página 106 de "The Good Parts". O primeiro verifica o construtor, mas fornecerá falsos negativos em diferentes frames ou janelas. Aqui está o segundo:

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

Crockford aponta que esta versão identificará o argumentsarray como um array, mesmo que não tenha nenhum dos métodos de array.

Sua interessante discussão do problema começa na página 105.

Há mais uma discussão interessante (pós-boas peças) aqui que inclui esta proposta:

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

Toda a discussão me faz nunca querer saber se algo é ou não um array.

Nosredna
fonte
1
isso interromperá o IE para objetos de strings e exclui primitivos de string, que são semelhantes a array, exceto no IE; verificar [[Class]] é melhor se você quiser matrizes reais; se você quiser objetos do tipo array, a verificação é muito restritiva
Christoph
@ Christoph - adicionei um pouco mais por meio de uma edição. Tópico fascinante.
Nosredna
2

jQuery implementa uma função isArray, que sugere que a melhor maneira de fazer isso é

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

(snippet retirado do jQuery v1.3.2 - ligeiramente ajustado para fazer sentido fora do contexto)

Mario Menger
fonte
Eles retornam falso no IE (# 2968). (De fonte jquery)
razzed
1
Esse comentário na fonte do jQuery parece se referir à função isFunction, não isArray
Mario Menger
4
você deve usar Object.prototype.toString()- isso é menos provável de quebrar
Christoph
2

Roubando do guru John Resig e jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
razzed
fonte
2
O segundo teste também retornaria verdadeiro para uma string: typeof "abc" .length === "número" // verdadeiro
Daniel Vandersluis
2
Além disso, acho que você nunca deve codificar nomes de tipo, como "número". Tente compará-lo com o número real, como typeof (obj) == typeof (42)
ohnoes
5
@mtod: por que os nomes dos tipos não devem ser codificados? afinal, os valores de retorno de typeofsão padronizados?
Christoph
1
@ohnoes explique-se
Pacerier
1

O que você vai fazer com o valor depois de decidir que é uma matriz?

Por exemplo, se você pretende enumerar os valores contidos, se parecer uma matriz OU se for um objeto sendo usado como uma tabela hash, o código a seguir obtém o que você deseja (este código para quando a função de fechamento retorna qualquer outro do que "indefinido". Observe que ele NÃO itera em contêineres COM ou enumerações; isso é deixado como um exercício para o leitor):

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

(Observação: "o! = Null" testa tanto para nulo quanto para indefinido)

Exemplos 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
fonte
embora a resposta possa ser interessante, ela realmente não tem nada a ver com a pergunta (e btw: iterar arrays via for..iné ruim [tm])
Christoph
@Christoph - Claro que sim. Deve haver algum motivo para decidir se algo é um Array: porque você deseja fazer algo com os valores. As coisas mais típicas (no meu código, pelo menos) são mapear, filtrar, pesquisar ou transformar os dados no array. A função acima faz exatamente isso: se a coisa passada tiver elementos que podem ser iterados, ela itera sobre eles. Se não, ele não faz nada e retorna indefinido sem causar danos.
James Hugard
@Christoph - Por que está iterando em matrizes com for..in bad [tm]? De que outra forma você iteraria em arrays e / ou hashtables (objetos)?
James Hugard
1
@James: for..initera sobre propriedades enumeráveis ​​de objetos; você não deve usá-lo com arrays porque: (1) é lento; (2) não há garantia de preservação da ordem; (3) incluirá qualquer propriedade definida pelo usuário configurada no objeto ou qualquer um de seus protótipos, já que ES3 não inclui nenhuma forma de configurar o atributo DontEnum; pode haver outros problemas que passaram pela minha mente
Christoph
1
@Christoph - Por outro lado, usar for (;;) não funcionará corretamente para arrays esparsos e não irá iterar as propriedades do objeto. # 3 é considerado formato incorreto para Objeto devido ao motivo que você mencionou. Por outro lado, você está TÃO certo com relação ao desempenho: for..in é ~ 36x mais lento do que for (;;) em uma matriz de elementos de 1 milhão. Uau. Infelizmente, não se aplica ao nosso caso de uso principal, que é a iteração das propriedades do objeto (hashtables).
James Hugard
1

Se você estiver fazendo isso no CouchDB (SpiderMonkey), use

Array.isArray(array)

como array.constructor === Arrayou array instanceof Arraynão funcionam. Usar array.toString() === "[object Array]"funciona, mas parece muito duvidoso em comparação.

Daniel Worthington-Bodart
fonte
Isso funciona no Node.js e nos navegadores também, não apenas no CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur
0

Se você quiser um navegador cruzado, deve jQuery.isArray .

Otto Allmendinger
fonte
0

No w3school, há um exemplo que deve ser bastante padrão.

Para verificar se uma variável é uma matriz, eles usam algo semelhante a este

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

testado no Chrome, Firefox, Safari, ie7

Eineki
fonte
usar constructorpara verificação de tipo é muito frágil; em vez disso, use uma das alternativas sugeridas
Christoph
porque você acha isso? Sobre frágil?
Kamarey
@Kamarey: constructoré uma propriedade DontEnum regular do objeto protótipo; isso pode não ser um problema para tipos integrados, desde que ninguém faça nada estúpido, mas para tipos definidos pelo usuário pode ser facilmente; meu conselho: sempre use instanceof, que verifica a cadeia de protótipo e não depende de propriedades que podem ser substituídas arbitrariamente
Christoph
1
Obrigado, encontrei outra explicação aqui: thinkweb2.com/projects/prototype/…
Kamarey
Isso não é confiável, porque o próprio objeto Array pode ser sobrescrito por um objeto personalizado.
Josh Stodola
-2

Uma das versões mais pesquisadas e discutidas dessa função pode ser encontrada no site do PHPJS . Você pode criar um link para pacotes ou ir diretamente para a função . Eu recomendo fortemente o site para equivalentes bem construídos de funções PHP em JavaScript.

Tony Miller
fonte
-2

Referência insuficiente igual aos construtores. Às vezes, eles têm diferentes referências de construtor. Então, eu uso representações de string deles.

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

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

amostras:

Array('44','55').constructor==Array retornar verdadeiro (IE8 / Chrome)

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

patrick72
fonte
3
Por que você substituiria a função correta por uma horrível?
Bergi