Come rilevare se una variabile è un array


101

Qual è il miglior metodo cross-browser standard di fatto per determinare se una variabile in JavaScript è un array o meno?

Cercando sul web ci sono una serie di suggerimenti diversi, alcuni buoni e alcuni non validi.

Ad esempio, il seguente è un approccio di base:

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

Tuttavia, nota cosa succede se l'array è vuoto, o obj in realtà non è un array ma implementa una proprietà length, ecc.

Quindi quale implementazione è la migliore in termini di funzionamento effettivo, cross-browser e prestazioni ancora efficienti?


4
Questo ritorno non è vero su una stringa?
James Hugard,

L'esempio fornito non è destinato a rispondere alla domanda in sé, è semplicemente un esempio di come una soluzione potrebbe essere affrontata - che spesso fallisce in casi speciali (come questo, da cui il "Tuttavia, nota ...").
stpe

@James: nella maggior parte dei browser (escluso IE), le stringhe sono simili ad array (cioè l'accesso tramite indici numerici è possibile)
Christoph

1
Non posso credere che sia così difficile da fare ...
Claudiu

Risposte:


160

Il controllo del tipo di oggetti in JS viene eseguito tramite instanceof, ie

obj instanceof Array

Questo non funzionerà se l'oggetto viene passato attraverso i confini del frame poiché ogni frame ha il proprio Arrayoggetto. È possibile aggirare questo problema controllando la proprietà [[Class]] interna dell'oggetto. Per ottenerlo, usa Object.prototype.toString()(questo è garantito per funzionare da ECMA-262):

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

Entrambi i metodi funzioneranno solo per array effettivi e non per oggetti simili a array come gli argumentselenchi di oggetti o nodi. Poiché tutti gli oggetti simili a un array devono avere una lengthproprietà numerica , controllerei questi in questo modo:

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

Tieni presente che le stringhe superano questo controllo, il che potrebbe causare problemi poiché IE non consente l'accesso ai caratteri di una stringa in base all'indice. Pertanto, potresti voler cambiare typeof obj !== 'undefined'in typeof obj === 'object'per escludere primitive e oggetti host con tipi distinti da 'object'alltogether. Ciò consentirà comunque il passaggio degli oggetti stringa, che dovrebbero essere esclusi manualmente.

Nella maggior parte dei casi, quello che vuoi veramente sapere è se puoi iterare sull'oggetto tramite indici numerici. Pertanto, potrebbe essere una buona idea controllare se l'oggetto ha invece una proprietà denominata 0, che può essere eseguita tramite uno di questi controlli:

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

Il cast in object è necessario per funzionare correttamente per primitive tipo array (cioè stringhe).

Ecco il codice per controlli affidabili per gli array JS:

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

e oggetti simili ad array iterabili (cioè non vuoti):

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
Ottimo riepilogo dello stato dell'arte nel controllo degli array js.
Nosredna

A partire da MS JS 5.6 (IE6?), L'operatore "instanceof" perdeva molta memoria quando veniva eseguito su un oggetto COM (ActiveXObject). Non ho controllato JS 5.7 o JS 5.8, ma questo potrebbe ancora essere vero.
James Hugard,

1
@ James: interessante - non sapevo di questa perdita; comunque, c'è una soluzione semplice: in IE, solo gli oggetti JS nativi hanno un hasOwnPropertymetodo, quindi basta anteporre instanceofa obj.hasOwnProperty && ; inoltre, è ancora un problema con IE7? i miei semplici test tramite task manager suggeriscono che la memoria è stata recuperata dopo aver ridotto a icona il browser ...
Christoph,

@Christoph - Non sono sicuro di IE7, ma IIRC non era nell'elenco delle correzioni di bug per JS 5.7 o 5.8. Ospitiamo il motore JS sottostante sul lato server in un servizio, quindi la riduzione al minimo dell'interfaccia utente non è applicabile.
James Hugard,

1
@TravisJ: vedere ECMA-262 5.1, sezione 15.2.4.2 ; i nomi delle classi interne sono per convenzione maiuscole - vedere la sezione 8.6.2
Christoph

42

L'arrivo di ECMAScript 5a edizione ci offre il metodo più sicuro per testare se una variabile è un array, Array.isArray () :

Array.isArray([]); // true

Sebbene la risposta accettata qui funzionerà su frame e finestre per la maggior parte dei browser, non lo fa per Internet Explorer 7 e versioni precedenti , perché Object.prototype.toStringverrà richiamato un array da una finestra diversa [object Object], no [object Array]. Anche IE 9 sembra essere regredito a questo comportamento (vedere la correzione aggiornata di seguito).

Se desideri una soluzione che funzioni su tutti i browser, puoi utilizzare:

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

Non è del tutto indistruttibile, ma sarebbe rotto solo da qualcuno che cercava di romperlo. Funziona intorno ai problemi in IE7 e versioni precedenti e IE9. Il bug esiste ancora in IE 10 PP2 , ma potrebbe essere corretto prima del rilascio.

PS, se non sei sicuro della soluzione, ti consiglio di testarla sul contenuto del tuo cuore e / o di leggere il post del blog. Ci sono altre potenziali soluzioni se ti senti a disagio nell'usare la compilazione condizionale.


La risposta accettata funziona bene in IE8 +, ma non in IE6,7
user123444555621

@ Pumbaa80: hai ragione :-) IE8 risolve il problema per il proprio Object.prototype.toString, ma testando un array creato in una modalità documento IE8 passato a una modalità documento IE7 o inferiore, il problema persiste. Avevo solo testato questo scenario e non il contrario. Ho modificato per applicare questa correzione solo a IE7 e versioni precedenti.
Andy E

WAAAAAAA, odio IE. Mi ero completamente dimenticato delle diverse "modalità documento", o "modalità schizo", come le chiamerò.
user123444555621

Anche se le idee sono fantastiche e sembra buono, questo non funziona per me in IE9 con finestre popup. Restituisce false per gli array creati dall'apri ... Esiste una soluzione compatibile con IE9? (Il problema sembra essere che IE9 implementa Array.isArray stesso, che restituisce false per il caso dato, quando non dovrebbe.
Steffen Heil

@Steffen: interessante, quindi l'implementazione nativa di isArraynon restituisce true dagli array creati in altre modalità documento? Dovrò esaminarlo quando avrò un po 'di tempo, ma penso che la cosa migliore da fare sia segnalare un bug su Connect in modo che possa essere risolto in IE 10.
Andy E

8

Crockford ha due risposte a pagina 106 di "The Good Parts". Il primo controlla il costruttore, ma darà falsi negativi su diversi frame o finestre. Ecco il secondo:

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

Crockford sottolinea che questa versione identificherà l' argumentsarray come array, anche se non ha nessuno dei metodi dell'array.

La sua interessante discussione sul problema inizia a pagina 105.

C'è un'ulteriore discussione interessante (post-Good Parts) qui che include questa proposta:

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

Tutta la discussione mi fa desiderare di non sapere se qualcosa è o meno un array.


1
questo si interromperà in IE per gli oggetti stringa ed esclude le primitive stringa, che sono simili ad array tranne che in IE; controllare [[Class]] è meglio se si vogliono array effettivi; se vuoi oggetti simili all'array, il controllo è troppo restrittivo
Christoph

@ Christoph - Ho aggiunto un po 'di più tramite una modifica. Argomento affascinante.
Nosredna

2

jQuery implementa una funzione isArray, che suggerisce che il modo migliore per farlo è

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

(frammento tratto da jQuery v1.3.2 - leggermente modificato per avere senso fuori contesto)


Restituiscono false su IE (# 2968). (Da jquery source)
Razzed il

1
Quel commento nella fonte jQuery sembra riferirsi alla funzione isFunction, non isArray
Mario Menger,

4
dovresti usare Object.prototype.toString()- è meno probabile che si rompa
Christoph

2

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

2
Il secondo test restituirà true anche per una stringa: typeof "abc" .length === "number" // true
Daniel Vandersluis,

2
Inoltre, immagino che non dovresti mai codificare i nomi dei tipi, come "numero". Prova invece a confrontarlo con il numero effettivo, ad esempio typeof (obj) == typeof (42)
ohnoes

5
@mtod: perché i nomi dei tipi non dovrebbero essere codificati? dopotutto, i valori di ritorno di typeofsono standardizzati?
Christoph

1
@ohnoes spiega te stesso
Pacerier

1

Cosa farai con il valore una volta deciso che è un array?

Ad esempio, se si intende enumerare i valori contenuti se sembra un array O se si tratta di un oggetto utilizzato come tabella hash, il codice seguente ottiene ciò che si desidera (questo codice si interrompe quando la funzione di chiusura restituisce qualcos'altro di "undefined". Si noti che NON itera su contenitori COM o enumerazioni; questo è lasciato come esercizio per il lettore):

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: "o! = Null" verifica sia null che undefined)

Esempi di utilizzo:

// 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] );
        }
    })
}

anche se la risposta potrebbe essere interessante, in realtà non ha nulla a che fare con la domanda (e btw: iterare su array tramite for..inè male [tm])
Christoph

@Christoph - Certo che lo fa. Ci deve essere un motivo per decidere se qualcosa è un array: perché vuoi fare qualcosa con i valori. Le cose più tipiche (nel mio codice, almeno) sono mappare, filtrare, cercare o trasformare in altro modo i dati nell'array. La funzione sopra fa esattamente questo: se la cosa passata ha elementi che possono essere ripetuti, allora itera su di essi. In caso contrario, non fa nulla e restituisce in modo innocuo indefinito.
James Hugard,

@Christoph - Perché iterare su array con for..in bad [tm]? In quale altro modo itereresti su array e / o hashtable (oggetti)?
James Hugard,

1
@ James: for..initera su proprietà enumerabili di oggetti; non dovresti usarlo con gli array perché: (1) è lento; (2) non è garantito il mantenimento dell'ordine; (3) includerà qualsiasi proprietà definita dall'utente impostata nell'oggetto o nei suoi prototipi poiché ES3 non include alcun modo per impostare l'attributo DontEnum; potrebbero esserci altri problemi che mi sono sfuggiti di mente
Christoph,

1
@Christoph - D'altra parte, l'uso di for (;;) non funzionerà correttamente per gli array sparsi e non itererà le proprietà degli oggetti. # 3 è considerato una cattiva forma per Object a causa del motivo che hai menzionato. D'altra parte, hai così ragione riguardo alle prestazioni: for..in è ~ 36 volte più lento di for (;;) su un array di 1M di elementi. Wow. Sfortunatamente, non applicabile al nostro caso d'uso principale, ovvero l'iterazione delle proprietà degli oggetti (tabelle hash).
James Hugard,



0

Su w3school c'è un esempio che dovrebbe essere abbastanza standard.

Per verificare se una variabile è un array, usano qualcosa di simile a questo

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

testato su Chrome, Firefox, Safari, ie7


l'uso constructorper il controllo del tipo è troppo fragile; usa invece una delle alternative suggerite
Christoph,

perchè la pensi così? A proposito di fragile?
Kamarey

@Kamarey: constructorè una proprietà DontEnum regolare dell'oggetto prototipo; questo potrebbe non essere un problema per i tipi incorporati fintanto che nessuno fa nulla di stupido, ma per i tipi definiti dall'utente lo può essere facilmente; il mio consiglio: usa sempre instanceof, che controlla la catena del prototipo e non si basa su proprietà che possono essere sovrascritte arbitrariamente
Christoph

1
Grazie, ho
Kamarey,

Questo non è affidabile, perché l'oggetto Array stesso può essere sovrascritto con un oggetto personalizzato.
Josh Stodola,

-2

Una delle versioni di questa funzione meglio studiate e discusse può essere trovata sul sito PHPJS . Puoi collegarti ai pacchetti oppure puoi andare direttamente alla funzione . Consiglio vivamente il sito per equivalenti ben costruiti di funzioni PHP in JavaScript.


-2

Riferimento insufficiente uguale dei costruttori. A volte hanno diversi riferimenti di costruttore. Quindi uso rappresentazioni di stringa di loro.

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

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

-4

Sostituisci Array.isArray(obj)conobj.constructor==Array

campioni :

Array('44','55').constructor==Array restituire vero (IE8 / Chrome)

'55'.constructor==Array return false (IE8 / Chrome)


3
Perché sostituire la funzione corretta con una orribile?
Bergi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.