Comment détecter si une variable est un tableau

101

Quelle est la meilleure méthode standard pour plusieurs navigateurs pour déterminer si une variable en JavaScript est un tableau ou non?

En recherchant sur le Web, il existe un certain nombre de suggestions différentes, certaines bonnes et quelques-unes invalides.

Par exemple, voici une approche de base:

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

Cependant, notez ce qui se passe si le tableau est vide ou si obj n'est en fait pas un tableau mais implémente une propriété length, etc.

Alors, quelle implémentation est la meilleure en termes de fonctionnement, de cross-browser et de performances efficaces?

stpe
la source
4
Cela ne retournera-t-il pas vrai sur une chaîne?
James Hugard
L'exemple donné n'est pas destiné à répondre à la question elle-même, c'est simplement un exemple de la façon dont une solution pourrait être abordée - qui échoue souvent dans des cas particuliers (comme celui-ci, d'où le "Cependant, remarquez ...").
stpe
@James: dans la plupart des navigateurs (à l'exclusion d'IE), les chaînes sont de type tableau (c'est-à-dire que l'accès via des indices numériques est possible)
Christoph
1
Je ne peux pas croire que c'est si difficile à faire ...
Claudiu

Réponses:

161

La vérification de type des objets dans JS se fait via instanceof, ie

obj instanceof Array

Cela ne fonctionnera pas si l'objet est passé à travers les limites du cadre, car chaque cadre a son propre Arrayobjet. Vous pouvez contourner ce problème en vérifiant la propriété [[Class]] interne de l'objet. Pour l'obtenir, utilisez Object.prototype.toString()(cela est garanti pour fonctionner par ECMA-262):

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

Les deux méthodes ne fonctionneront que pour les tableaux réels et non pour les objets de type tableau comme les argumentslistes d'objets ou de nœuds. Comme tous les objets de type tableau doivent avoir une lengthpropriété numérique , je les vérifierais comme ceci:

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

Veuillez noter que les chaînes passeront cette vérification, ce qui pourrait entraîner des problèmes car IE n'autorise pas l'accès aux caractères d'une chaîne par index. Par conséquent, vous souhaiterez peut-être passer typeof obj !== 'undefined'à typeof obj === 'object'pour exclure les primitives et les objets hôtes avec des types distincts de 'object'tous. Cela laissera toujours passer les objets string, qui devront être exclus manuellement.

Dans la plupart des cas, ce que vous voulez vraiment savoir, c'est si vous pouvez parcourir l'objet via des indices numériques. Par conséquent, il peut être judicieux de vérifier si l'objet a une propriété nommée à la 0place, ce qui peut être fait via l'une de ces vérifications:

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

Le transtypage en objet est nécessaire pour fonctionner correctement pour les primitives de type tableau (c'est-à-dire les chaînes).

Voici le code pour des vérifications robustes pour les tableaux JS:

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

et objets de type tableau itérables (c'est-à-dire non vides):

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
la source
7
Excellent résumé de l'état de l'art de la vérification de tableau js.
Nosredna
Depuis MS JS 5.6 (IE6?), L'opérateur "instanceof" a perdu beaucoup de mémoire lorsqu'il est exécuté sur un objet COM (ActiveXObject). Je n'ai pas vérifié JS 5.7 ou JS 5.8, mais cela peut toujours être vrai.
James Hugard
1
@James: intéressant - je ne connaissais pas cette fuite; de toute façon, il y a une solution simple: dans IE, seuls les objets JS natifs ont une hasOwnPropertyméthode, donc préfixez simplement votre instanceofavec obj.hasOwnProperty && ; aussi, est-ce toujours un problème avec IE7? mes tests simples via le gestionnaire de tâches suggèrent que la mémoire a été récupérée après avoir minimisé le navigateur ...
Christoph
@Christoph - Je ne suis pas sûr d'IE7, mais IIRC ne figurait pas sur la liste des corrections de bogues pour JS 5.7 ou 5.8. Nous hébergeons le moteur JS sous-jacent côté serveur dans un service, donc minimiser l'interface utilisateur n'est pas applicable.
James Hugard
1
@TravisJ: voir ECMA-262 5.1, section 15.2.4.2 ; les noms de classe internes sont par convention en majuscules - voir section 8.6.2
Christoph
43

L'arrivée d'ECMAScript 5e édition nous donne la méthode la plus sûre pour tester si une variable est un tableau, Array.isArray () :

Array.isArray([]); // true

Alors que la réponse acceptée ici fonctionnera dans les cadres et les fenêtres pour la plupart des navigateurs, ce n'est pas le cas pour Internet Explorer 7 et les versions antérieures , car Object.prototype.toStringappelé sur un tableau à partir d'une fenêtre différente reviendra [object Object], non [object Array]. IE 9 semble avoir régressé vers ce comportement également (voir le correctif mis à jour ci-dessous).

Si vous souhaitez une solution qui fonctionne sur tous les navigateurs, vous pouvez utiliser:

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

Ce n'est pas entièrement incassable, mais il ne serait brisé que par quelqu'un qui essaie de le casser. Cela fonctionne autour des problèmes dans IE7 et inférieur et IE9. Le bogue existe toujours dans IE 10 PP2 , mais il pourrait être corrigé avant la publication.

PS, si vous n'êtes pas sûr de la solution, je vous recommande de la tester à votre guise et / ou de lire le billet de blog. Il existe d'autres solutions potentielles si vous ne vous sentez pas à l'aise avec la compilation conditionnelle.

Andy E
la source
La réponse acceptée fonctionne bien dans IE8 +, mais pas dans IE6,7
user123444555621
@ Pumbaa80: Vous avez raison :-) IE8 corrige le problème pour son propre Object.prototype.toString, mais en testant un tableau créé dans un mode document IE8 qui est passé à un mode document IE7 ou inférieur, le problème persiste. Je n'avais testé que ce scénario et non l'inverse. J'ai modifié pour appliquer ce correctif uniquement à IE7 et inférieur.
Andy E
WAAAAAAA, je déteste IE. J'ai totalement oublié les différents "modes document", ou "modes schizo", comme je vais les appeler.
user123444555621
Bien que les idées soient excellentes et qu'elles aient l'air bien, cela ne fonctionne pas pour moi dans IE9 avec des fenêtres contextuelles. Il retourne false pour les tableaux créés par l'opener ... Existe-t-il une solution compatible IE9? (Le problème semble être que IE9 implémente Array.isArray lui-même, qui renvoie false pour le cas donné, alors qu'il ne devrait pas.
Steffen Heil
@Steffen: intéressant, donc l'implémentation native de isArrayne renvoie pas true à partir de tableaux créés dans d'autres modes de document? Je devrai me pencher là-dessus quand j'aurai le temps, mais je pense que la meilleure chose à faire est de signaler un bogue sur Connect afin qu'il puisse être corrigé dans IE 10.
Andy E
8

Crockford a deux réponses à la page 106 de «The Good Parts». Le premier vérifie le constructeur, mais donnera de faux négatifs sur différents cadres ou fenêtres. Voici le second:

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

Crockford souligne que cette version identifiera le argumentstableau comme un tableau, même s'il ne dispose d'aucune des méthodes de tableau.

Son analyse intéressante du problème commence à la page 105.

Il y a une autre discussion intéressante (post-Good Parts) ici qui comprend cette proposition:

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

Toute la discussion me fait ne jamais vouloir savoir si quelque chose est ou non un tableau.

Nosredna
la source
1
cela va casser dans IE pour les objets chaînes et exclut les primitives de chaîne, qui sont de type tableau sauf dans IE; vérifier [[Class]] est préférable si vous voulez des tableaux réels; si vous voulez des objets de type tableau, la vérification est trop restrictive
Christoph
@ Christoph - J'ai ajouté un peu plus via une modification. Sujet fascinant.
Nosredna
2

jQuery implémente une fonction isArray, ce qui suggère que la meilleure façon de le faire est

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

(extrait de jQuery v1.3.2 - légèrement ajusté pour avoir un sens hors contexte)

Mario Menger
la source
Ils renvoient false sur IE (# 2968). (De source jquery)
razzed le
1
Ce commentaire dans la source jQuery semble faire référence à la fonction isFunction, pas à isArray
Mario Menger
4
vous devriez utiliser Object.prototype.toString()- c'est moins susceptible de casser
Christoph
2

Voler le gourou John Resig et jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
ébloui
la source
2
Le deuxième test renverrait également vrai pour une chaîne: typeof "abc" .length === "number" // true
Daniel Vandersluis
2
De plus, je suppose que vous ne devriez jamais coder en dur les noms de type, comme "nombre". Essayez plutôt de le comparer au nombre réel, comme typeof (obj) == typeof (42)
ohnoes
5
@mtod: pourquoi les noms de types ne devraient-ils pas être codés en dur? après tout, les valeurs de retour de typeofsont standardisées?
Christoph le
1
@ohnoes explique vous
Pacerier
1

Qu'allez-vous faire avec la valeur une fois que vous décidez qu'il s'agit d'un tableau?

Par exemple, si vous avez l'intention d'énumérer les valeurs contenues si cela ressemble à un tableau OU s'il s'agit d'un objet utilisé comme table de hachage, le code suivant obtient ce que vous voulez (ce code s'arrête lorsque la fonction de fermeture renvoie autre chose que "indéfini". Notez qu'il n'itère PAS sur les conteneurs COM ou les énumérations; cela reste un exercice pour le lecteur):

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

(Remarque: "o! = Null" teste à la fois null et indéfini)

Exemples d'utilisation:

// 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
la source
bien que la réponse puisse être intéressante, elle n'a vraiment rien à voir avec la question (et btw: itérer sur des tableaux via for..inest mauvais [tm])
Christoph
@Christoph - Bien sûr que oui. Il doit y avoir une raison pour décider si quelque chose est un tableau: parce que vous voulez faire quelque chose avec les valeurs. Les choses les plus courantes (au moins dans mon code) sont de mapper, filtrer, rechercher ou transformer les données du tableau. La fonction ci-dessus fait exactement cela: si l'objet passé a des éléments qui peuvent être itérés, alors il itère sur eux. Sinon, il ne fait rien et retourne indéfiniment sans danger.
James Hugard
@Christoph - Pourquoi itérer sur des tableaux avec for..in bad [tm]? Sinon, comment feriez-vous une itération sur des tableaux et / ou des hashtables (objets)?
James Hugard
1
@James: for..initère sur les propriétés énumérables des objets; vous ne devriez pas l'utiliser avec des tableaux car: (1) c'est lent; (2) il n'est pas garanti de préserver l'ordre; (3) il inclura toute propriété définie par l'utilisateur définie dans l'objet ou l'un de ses prototypes car ES3 n'inclut aucun moyen de définir l'attribut DontEnum; il y a peut-être d'autres problèmes qui m'ont échappé
Christoph
1
@Christoph - D'un autre côté, l'utilisation de for (;;) ne fonctionnera pas correctement pour les tableaux épars et n'itérera pas les propriétés des objets. # 3 est considéré comme une mauvaise forme pour Object en raison de la raison que vous mentionnez. D'un autre côté, vous avez tellement raison en ce qui concerne les performances: for..in est ~ 36x plus lent que for (;;) sur un tableau d'éléments de 1M. Sensationnel. Malheureusement, cela ne s'applique pas à notre cas d'utilisation principal, qui consiste à itérer les propriétés des objets (tables de hachage).
James Hugard
1

Si vous faites cela dans CouchDB (SpiderMonkey), utilisez

Array.isArray(array)

comme array.constructor === Arrayou array instanceof Arrayne fonctionne pas. L'utilisation array.toString() === "[object Array]"fonctionne mais semble assez douteuse en comparaison.

Daniel Worthington-Bodart
la source
Cela fonctionne dans Node.js et dans les navigateurs aussi, pas seulement CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur
0

Si vous voulez plusieurs navigateurs, vous voulez jQuery.isArray .

Otto Allmendinger
la source
0

Sur w3school, il y a un exemple qui devrait être assez standard.

Pour vérifier si une variable est un tableau, ils utilisent quelque chose de similaire à ceci

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

testé sur Chrome, Firefox, Safari, ie7

Eineki
la source
l'utilisation constructorpour la vérification de type est trop fragile; utilisez plutôt l'une des alternatives suggérées
Christoph
Pourquoi penses-tu ça? À propos de fragile?
Kamarey le
@Kamarey: constructorest une propriété DontEnum régulière de l'objet prototype; cela peut ne pas être un problème pour les types intégrés tant que personne ne fait rien de stupide, mais pour les types définis par l'utilisateur, cela peut facilement l'être; mon conseil: toujours utiliser instanceof, qui vérifie la chaîne de prototypes et ne repose pas sur des propriétés qui peuvent être écrasées arbitrairement
Christoph
1
Merci, j'ai
Kamarey
Ce n'est pas fiable, car l'objet Array lui-même peut être écrasé par un objet personnalisé.
Josh Stodola le
-2

L'une des versions les mieux étudiées et discutées de cette fonction se trouve sur le site PHPJS . Vous pouvez créer un lien vers des packages ou accéder directement à la fonction . Je recommande vivement le site pour des équivalents bien construits de fonctions PHP en JavaScript.

Tony Miller
la source
-2

Pas assez de références égales aux constructeurs. Parfois, ils ont différentes références de constructeur. J'utilise donc des représentations sous forme de chaîne.

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}
kuy
la source
dupé par{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi
-4

Remplacer Array.isArray(obj)parobj.constructor==Array

échantillons:

Array('44','55').constructor==Array retourne vrai (IE8 / Chrome)

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

patrick72
la source
3
Pourquoi remplaceriez-vous le bon fonctionnement par un horrible?
Bergi