So erkennen Sie, ob eine Variable ein Array ist

101

Was ist die beste de-facto standardmäßige browserübergreifende Methode, um festzustellen, ob eine Variable in JavaScript ein Array ist oder nicht?

Beim Durchsuchen des Webs gibt es eine Reihe verschiedener Vorschläge, von denen einige gut und einige ungültig sind.

Das Folgende ist beispielsweise ein grundlegender Ansatz:

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

Beachten Sie jedoch, was passiert, wenn das Array leer ist oder obj tatsächlich kein Array ist, sondern eine Längeneigenschaft usw. implementiert.

Welche Implementierung ist die beste, um tatsächlich zu arbeiten, browserübergreifend zu sein und dennoch effizient zu arbeiten?

stpe
quelle
4
Wird diese Rückgabe für eine Zeichenfolge nicht wahr sein?
James Hugard
Das gegebene Beispiel dient nicht zur Beantwortung der Frage selbst, sondern ist lediglich ein Beispiel dafür, wie eine Lösung angegangen werden könnte - was in besonderen Fällen häufig fehlschlägt (wie dieses, daher das "jedoch beachten ...").
Stpe
@ James: In den meisten Browsern (IE ausgeschlossen) sind Zeichenfolgen Array-ähnlich (dh der Zugriff über numerische Indizes ist möglich)
Christoph
1
Ich kann nicht glauben, dass dies so schwierig ist ...
Claudiu

Antworten:

160

Die Typprüfung von Objekten in JS erfolgt über instanceof, dh

obj instanceof Array

Dies funktioniert nicht, wenn das Objekt über Frame-Grenzen hinweg übergeben wird, da jeder Frame ein eigenes ArrayObjekt hat. Sie können dies umgehen, indem Sie die interne [[Klasse]] - Eigenschaft des Objekts überprüfen . Verwenden Sie dazu Object.prototype.toString()(dies funktioniert garantiert mit ECMA-262):

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

Beide Methoden funktionieren nur für tatsächliche Arrays und nicht für Array-ähnliche Objekte wie die argumentsObjekt- oder Knotenlisten. Da alle Array-ähnlichen Objekte eine numerische lengthEigenschaft haben müssen, würde ich Folgendes überprüfen:

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

Bitte beachten Sie, dass Zeichenfolgen diese Prüfung bestehen, was zu Problemen führen kann, da der IE den Zugriff auf die Zeichen einer Zeichenfolge nach Index nicht zulässt. Daher möchten Sie möglicherweise zu ändern typeof obj !== 'undefined', typeof obj === 'object'um 'object'Grundelemente und Hostobjekte mit Typen auszuschließen, die sich von insgesamt unterscheiden. Dadurch werden weiterhin Zeichenfolgenobjekte passieren, die manuell ausgeschlossen werden müssten.

In den meisten Fällen möchten Sie tatsächlich wissen, ob Sie über numerische Indizes über das Objekt iterieren können. Daher ist es möglicherweise eine gute Idee, zu überprüfen, ob das Objekt 0stattdessen eine Eigenschaft mit dem Namen hat. Dies kann über eine der folgenden Überprüfungen erfolgen:

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

Die Umwandlung in ein Objekt ist erforderlich, um für Array-ähnliche Grundelemente (dh Zeichenfolgen) ordnungsgemäß zu funktionieren.

Hier ist der Code für zuverlässige Überprüfungen von JS-Arrays:

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

und iterierbare (dh nicht leere) Array-ähnliche Objekte:

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
quelle
7
Hervorragende Zusammenfassung des Standes der Technik bei der Array-Überprüfung.
Nosredna
Ab MS JS 5.6 (IE6?) Hat der Operator "instanceof" viel Speicher verloren, wenn er für ein COM-Objekt (ActiveXObject) ausgeführt wurde. Habe JS 5.7 oder JS 5.8 nicht überprüft, aber dies kann immer noch zutreffen.
James Hugard
1
@ James: interessant - ich wusste nichts von diesem Leck; wie auch immer, es gibt eine einfache Lösung: in IE, nur native JS Objekte haben eine hasOwnPropertyMethode, so dass nur das Präfix Ihrer instanceofmit obj.hasOwnProperty && ; Ist dies auch immer noch ein Problem mit IE7? Meine einfachen Tests über den Task-Manager legen nahe, dass der Speicher nach Minimierung des Browsers wiederhergestellt wurde ...
Christoph
@Christoph - Ich bin mir nicht sicher über IE7, aber IIRC war nicht auf der Liste der Bugfixes für JS 5.7 oder 5.8. Wir hosten die zugrunde liegende JS-Engine auf der Serverseite in einem Dienst, sodass eine Minimierung der Benutzeroberfläche nicht anwendbar ist.
James Hugard
1
@TravisJ: siehe ECMA-262 5.1, Abschnitt 15.2.4.2 ; Interne Klassennamen werden gemäß
Christoph,
42

Mit der Einführung von ECMAScript 5th Edition können wir am sichersten testen, ob eine Variable ein Array ist: Array.isArray () :

Array.isArray([]); // true

Während die hier akzeptierte Antwort für die meisten Browser über Frames und Fenster hinweg funktioniert , gilt dies nicht für Internet Explorer 7 und niedriger , da ein Object.prototype.toStringAufruf eines Arrays aus einem anderen Fenster [object Object]nicht zurückgegeben wird [object Array]. IE 9 scheint ebenfalls auf dieses Verhalten zurückgegangen zu sein (siehe aktualisierte Korrektur unten).

Wenn Sie eine Lösung suchen, die in allen Browsern funktioniert, können Sie Folgendes verwenden:

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

Es ist nicht ganz unzerbrechlich, aber es würde nur von jemandem gebrochen, der sich bemüht, es zu brechen. Es umgeht die Probleme in IE7 und niedriger und IE9. Der Fehler ist in IE 10 PP2 weiterhin vorhanden , wurde jedoch möglicherweise vor der Veröffentlichung behoben.

PS, wenn Sie sich über die Lösung nicht sicher sind, empfehle ich Ihnen, sie nach Herzenslust zu testen und / oder den Blog-Beitrag zu lesen. Es gibt andere mögliche Lösungen, wenn Sie sich mit der bedingten Kompilierung nicht wohl fühlen.

Andy E.
quelle
Die akzeptierte Antwort funktioniert gut in IE8 +, aber nicht in IE6,7
user123444555621
@ Pumbaa80: Sie haben Recht :-) IE8 behebt das Problem für seinen eigenen Object.prototype.toString. Wenn Sie jedoch ein Array testen, das in einem IE8-Dokumentmodus erstellt wurde, der an einen IE7- oder niedrigeren Dokumentmodus übergeben wird, bleibt das Problem bestehen. Ich hatte nur dieses Szenario getestet und nicht umgekehrt. Ich habe bearbeitet, um dieses Update nur auf IE7 und niedriger anzuwenden.
Andy E
WAAAAAAA, ich hasse IE. Ich habe die verschiedenen "Dokumentmodi" oder "Schizo-Modi", wie ich sie nennen werde, völlig vergessen.
user123444555621
Obwohl die Ideen großartig sind und gut aussehen, funktioniert dies in IE9 mit Popup-Fenstern nicht für mich. Für vom Opener erstellte Arrays wird false zurückgegeben. Gibt es IE9-kompatible Lösungen? (Das Problem scheint zu sein, dass IE9 Array.isArray selbst implementiert, was für den gegebenen Fall false zurückgibt, wenn dies nicht der Fall sein sollte.
Steffen Heil
@Steffen: Interessant, also gibt die native Implementierung von isArraynicht wahr von Arrays zurück, die in anderen Dokumentmodi erstellt wurden? Ich muss mich darum kümmern, wenn ich etwas Zeit habe, aber ich denke, das Beste, was ich tun kann, ist, einen Fehler in Connect zu melden, damit er in IE 10 behoben werden kann.
Andy E
8

Crockford hat zwei Antworten auf Seite 106 von "The Good Parts". Der erste überprüft den Konstruktor, gibt jedoch über verschiedene Frames oder Fenster hinweg falsche Negative aus. Hier ist der zweite:

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

Crockford weist darauf hin, dass diese Version das argumentsArray als Array identifiziert , obwohl es keine der Array-Methoden enthält.

Seine interessante Diskussion des Problems beginnt auf Seite 105.

Es gibt hier eine weitere interessante Diskussion (Post-Good Parts) , die diesen Vorschlag enthält:

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

Bei all den Diskussionen möchte ich nie wissen, ob etwas ein Array ist oder nicht.

Nosredna
quelle
1
Dies wird im IE für Zeichenfolgenobjekte unterbrochen und schließt Zeichenfolgenprimitive aus, die außer im IE Array-ähnlich sind. Das Überprüfen von [[Klasse]] ist besser, wenn Sie tatsächliche Arrays möchten. Wenn Sie Array-ähnliche Objekte möchten, ist die Prüfung imo zu restriktiv
Christoph
@ Christoph - Ich habe ein bisschen mehr über eine Bearbeitung hinzugefügt. Faszinierendes Thema.
Nosredna
2

jQuery implementiert eine isArray-Funktion, die den besten Weg vorschlägt, dies zu tun

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

(Ausschnitt aus jQuery v1.3.2 - leicht angepasst, um außerhalb des Kontexts einen Sinn zu ergeben)

Mario Menger
quelle
Sie geben im IE false zurück (# 2968). (Aus jquery source)
Razzed
1
Dieser Kommentar in der jQuery-Quelle scheint sich auf die Funktion isFunction zu beziehen, nicht auf isArray
Mario Menger
4
Sie sollten verwenden Object.prototype.toString()- das ist weniger wahrscheinlich zu brechen
Christoph
2

Diebstahl von Guru John Resig und jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
razzed
quelle
2
Der zweite Test würde auch für einen String true zurückgeben: typeof "abc" .length === "number" // true
Daniel Vandersluis
2
Außerdem sollten Sie niemals Typnamen wie "Nummer" fest codieren. Versuchen Sie stattdessen, es mit der tatsächlichen Zahl zu vergleichen, wie typeof (obj) == typeof (42)
ohnoes
5
@mtod: Warum sollten Typnamen nicht fest codiert werden? Immerhin sind die Rückgabewerte von typeofstandardisiert?
Christoph
1
@ohnoes erklären Sie sich
Pacerier
1

Was machen Sie mit dem Wert, wenn Sie sich für ein Array entschieden haben?

Wenn Sie beispielsweise die enthaltenen Werte auflisten möchten , wenn sie wie ein Array aussehen, ODER wenn es sich um ein Objekt handelt, das als Hash-Tabelle verwendet wird, erhält der folgende Code das, was Sie möchten (dieser Code stoppt, wenn die Schließfunktion etwas anderes zurückgibt als "undefiniert". Beachten Sie, dass es NICHT über COM-Container oder Aufzählungen iteriert; dies bleibt als Übung für den Leser):

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

(Hinweis: "o! = Null" -Tests für null und undefiniert)

Anwendungsbeispiele:

// 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
quelle
Obwohl die Antwort interessant sein mag, hat sie eigentlich nichts mit der Frage zu tun (und übrigens: das Überlaufen von Arrays über for..inist schlecht [tm])
Christoph
@Christoph - Sicher tut es. Es muss einen Grund geben, zu entscheiden, ob etwas ein Array ist: weil Sie etwas mit den Werten tun möchten. Die typischsten Dinge (zumindest in meinem Code) sind das Zuordnen, Filtern, Suchen oder anderweitige Transformieren der Daten im Array. Die obige Funktion macht genau das: Wenn das übergebene Objekt Elemente enthält, über die iteriert werden kann, wird es über diese iteriert. Wenn nicht, dann tut es nichts und gibt harmlos undefiniert zurück.
James Hugard
@Christoph - Warum iteriert man über Arrays mit for..in bad [tm]? Wie würden Sie sonst über Arrays und / oder Hashtabellen (Objekte) iterieren?
James Hugard
1
@James: for..initeriert über aufzählbare Eigenschaften von Objekten; Sie sollten es nicht mit Arrays verwenden, weil: (1) es langsam ist; (2) es wird nicht garantiert, dass die Ordnung erhalten bleibt; (3) Es enthält alle benutzerdefinierten Eigenschaften, die im Objekt oder einem seiner Prototypen festgelegt sind, da ES3 keine Möglichkeit zum Festlegen des DontEnum-Attributs enthält. Es könnte andere Probleme geben, die mir durch den Kopf
Christoph
1
@Christoph - Andererseits funktioniert die Verwendung von for (;;) für dünn besetzte Arrays nicht ordnungsgemäß und es werden keine Objekteigenschaften iteriert. # 3 wird aus dem von Ihnen genannten Grund als schlechte Form für Object angesehen. Andererseits haben Sie hinsichtlich der Leistung SO Recht: for..in ist ~ 36x langsamer als for (;;) auf einem 1M-Elementarray. Beeindruckend. Leider nicht anwendbar auf unseren Hauptanwendungsfall, bei dem Objekteigenschaften (Hashtabellen) iteriert werden.
James Hugard
1

Wenn Sie dies in CouchDB (SpiderMonkey) tun, verwenden Sie

Array.isArray(array)

wie array.constructor === Arrayoder array instanceof Arraynicht funktionieren. Die Verwendung array.toString() === "[object Array]"funktioniert, scheint aber im Vergleich ziemlich zwielichtig zu sein.

Daniel Worthington-Bodart
quelle
Dies funktioniert in Node.js und auch in den Browsern, nicht nur in CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur
0

Wenn Sie browserübergreifend möchten, möchten Sie jQuery.isArray .

Otto Allmendinger
quelle
0

Auf w3school gibt es ein Beispiel, das durchaus Standard sein sollte.

Um zu überprüfen, ob eine Variable ein Array ist, verwenden sie etwas Ähnliches

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

getestet auf Chrome, Firefox, Safari, ie7

Eineki
quelle
Verwendung constructorfür die Typprüfung ist imo zu spröde; Verwenden Sie stattdessen eine der vorgeschlagenen Alternativen
Christoph
warum denkst du das? Über spröde?
Kamarey
@Kamarey: constructorist eine reguläre DontEnum-Eigenschaft des Prototypobjekts. Dies ist möglicherweise kein Problem für integrierte Typen, solange niemand etwas Dummes tut, aber für benutzerdefinierte Typen kann dies leicht sein. Mein Rat: Immer verwenden instanceof, das die Prototypenkette überprüft und sich nicht auf Eigenschaften stützt, die willkürlich überschrieben werden können
Christoph
1
Vielen Dank, fand eine andere Erklärung hier: thinkweb2.com/projects/prototype/…
Kamarey
Dies ist nicht zuverlässig, da das Array-Objekt selbst mit einem benutzerdefinierten Objekt überschrieben werden kann.
Josh Stodola
-2

Eine der am besten recherchierten und diskutierten Versionen dieser Funktion finden Sie auf der PHPJS-Website . Sie können auf Pakete verlinken oder direkt zur Funktion gehen . Ich empfehle die Site sehr für gut konstruierte Äquivalente von PHP-Funktionen in JavaScript.

Tony Miller
quelle
-2

Nicht genügend Referenz gleich Konstruktoren. Manchmal haben sie unterschiedliche Referenzen des Konstruktors. Also benutze ich String-Darstellungen von ihnen.

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}
kuy
quelle
getäuscht von{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi
-4

Ersetzen Array.isArray(obj)durchobj.constructor==Array

Proben :

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

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

patrick72
quelle
3
Warum sollten Sie die korrekte Funktion durch eine schreckliche ersetzen?
Bergi