Jak wykryć, czy zmienna jest tablicą

102

Jaka jest najlepsza de facto standardowa metoda w różnych przeglądarkach do określenia, czy zmienna w JavaScript jest tablicą, czy nie?

Przeszukując sieć można znaleźć wiele różnych sugestii, niektóre są dobre, a niektóre są nieprawidłowe.

Na przykład poniższe podejście jest podstawowym podejściem:

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

Zwróć jednak uwagę, co się stanie, jeśli tablica jest pusta lub obj w rzeczywistości nie jest tablicą, ale implementuje właściwość length itp.

Więc która implementacja jest najlepsza pod względem faktycznego działania, bycia na różnych przeglądarkach i nadal wydajnego działania?

stpe
źródło
5
Czy to nie zwróci prawdy w ciągu?
James Hugard
Podany przykład nie ma na celu udzielenia odpowiedzi na samo pytanie, jest jedynie przykładem tego, jak można podejść do rozwiązania - co często zawodzi w szczególnych przypadkach (takich jak ten, stąd „Jednak uwaga ...”).
stpe
@James: w większości przeglądarek (z wyłączeniem IE) ciągi znaków przypominają tablice (tzn. Możliwy jest dostęp przez indeksy numeryczne)
Christoph
1
nie mogę uwierzyć, że to takie trudne ...
Claudiu

Odpowiedzi:

162

Sprawdzanie typów obiektów w JS odbywa się za pomocą instanceof, np

obj instanceof Array

To nie zadziała, jeśli obiekt zostanie przekazany poza granice klatki, ponieważ każda klatka ma swój własny Arrayobiekt. Możesz obejść ten problem, sprawdzając wewnętrzną właściwość [[Class]] obiektu. Aby go zdobyć, użyj Object.prototype.toString()(to gwarantuje działanie ECMA-262):

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

Obie metody będą działać tylko dla rzeczywistych tablic, a nie obiektów przypominających tablice, takich jak listy argumentsobiektów lub węzłów. Ponieważ wszystkie obiekty podobne do tablic muszą mieć lengthwłaściwość numeryczną , sprawdziłbym to następująco:

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

Należy pamiętać, że ciągi przejdą tę kontrolę, co może prowadzić do problemów, ponieważ IE nie zezwala na dostęp do znaków ciągu według indeksu. Dlatego możesz chcieć zmienić typeof obj !== 'undefined'na, typeof obj === 'object'aby wykluczyć prymitywy i obiekty hosta z typami różniącymi się od wszystkich 'object'. To nadal będzie przepuszczać obiekty tekstowe, które musiałyby zostać wykluczone ręcznie.

W większości przypadków tak naprawdę chcesz wiedzieć, czy możesz iterować po obiekcie za pomocą indeksów liczbowych. Dlatego dobrym pomysłem może być sprawdzenie, czy obiekt ma 0zamiast tego nazwaną właściwość , co można zrobić za pomocą jednego z poniższych sprawdzeń:

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

Rzutowanie na obiekt jest niezbędne do poprawnego działania dla prymitywów przypominających tablice (tj. Łańcuchów).

Oto kod do niezawodnych kontroli tablic JS:

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

i iterowalne (tj. niepuste) obiekty przypominające tablice:

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
źródło
7
Świetne podsumowanie najnowocześniejszych metod sprawdzania tablic js.
Nosredna
Począwszy od MS JS 5.6 (IE6?), Operator „instanceof” wycieka dużo pamięci, gdy jest uruchamiany na obiekcie COM (ActiveXObject). Nie sprawdzałem JS 5.7 ani JS 5.8, ale może to nadal być prawdą.
James Hugard
1
@James: ciekawe - nie wiedziałem o tym wycieku; w każdym razie, jest łatwa poprawka: w IE tylko natywne obiekty JS mają hasOwnPropertymetodę, więc po prostu przedrostek instanceofz obj.hasOwnProperty && ; czy jest to nadal problem z IE7? moje proste testy za pośrednictwem menedżera zadań sugerują, że pamięć została odzyskana po zminimalizowaniu przeglądarki ...
Christoph
@Christoph - Nie jestem pewien co do IE7, ale IIRC tego nie było na liście poprawek błędów dla JS 5.7 lub 5.8. Hostujemy podstawowy silnik JS po stronie serwera w usłudze, więc minimalizacja interfejsu użytkownika nie ma zastosowania.
James Hugard
1
@TravisJ: patrz ECMA-262 5.1, sekcja 15.2.4.2 ; wewnętrzne nazwy klas są zgodnie z konwencją dużymi literami - patrz rozdział 8.6.2
Christoph,
43

Pojawienie się 5. edycji ECMAScript daje nam najpewniejszą metodę testowania, jeśli zmienna jest tablicą, Array.isArray () :

Array.isArray([]); // true

Chociaż zaakceptowana odpowiedź będzie działać w ramkach i oknach w większości przeglądarek, nie w przypadku Internet Explorera 7 i starszych , ponieważ Object.prototype.toStringwywołana tablica z innego okna zwróci [object Object], nie [object Array]. Wydaje się, że IE 9 również cofnął się do tego zachowania (zobacz zaktualizowaną poprawkę poniżej).

Jeśli potrzebujesz rozwiązania działającego we wszystkich przeglądarkach, możesz użyć:

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

Nie jest całkowicie niezniszczalny, ale zostałby złamany tylko przez kogoś, kto bardzo się stara. Działa wokół problemów w IE7 i wcześniejszych oraz IE9. Błąd nadal istnieje w IE 10 PP2 , ale może zostać naprawiony przed wydaniem.

PS, jeśli nie masz pewności co do rozwiązania, polecam przetestowanie go do treści i / lub przeczytanie wpisu na blogu. Istnieją inne potencjalne rozwiązania, jeśli nie czujesz się komfortowo przy użyciu kompilacji warunkowej.

Andy E.
źródło
Zaakceptowana odpowiedź działa dobrze w IE8 +, ale nie w IE6,7
user123444555621
@ Pumbaa80: Masz rację :-) IE8 rozwiązuje problem z własnym Object.prototype.toString, ale testując tablicę utworzoną w trybie dokumentu IE8, która jest przekazywana do trybu dokumentu IE7 lub niższego, problem nadal występuje. Przetestowałem tylko ten scenariusz, a nie odwrotnie. Edytowałem, aby zastosować tę poprawkę tylko do IE7 i niższych.
Andy E
WAAAAAAA, nienawidzę IE. Zupełnie zapomniałem o różnych „trybach dokumentów” lub „trybach schizo”, jak je nazywam.
user123444555621
Chociaż pomysły są świetne i wygląda dobrze, to nie działa dla mnie w IE9 z wyskakującymi okienkami. Zwraca false dla tablic utworzonych przez otwieracz ... Czy istnieją rozwiązania zgodne z IE9? (Problem wydaje się polegać na tym, że IE9 implemnets Array.isArray sam, który zwraca wartość false dla danego przypadku, podczas gdy nie powinien.
Steffen Heil
@Steffen: ciekawe, więc natywna implementacja isArray nie zwraca true z tablic utworzonych w innych trybach dokumentów? Będę musiał się temu przyjrzeć, gdy będę miał trochę czasu, ale myślę, że najlepiej będzie zgłosić błąd w Connect, aby można go było naprawić w IE 10.
Andy E
8

Crockford ma dwie odpowiedzi na stronie 106 „Dobrej części”. Pierwsza z nich sprawdza konstruktora, ale daje fałszywe negatywy w różnych ramkach lub oknach. Oto druga:

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

Crockford zwraca uwagę, że ta wersja zidentyfikuje argumentstablicę jako tablicę, mimo że nie ma żadnej z metod tablicowych.

Jego interesujące omówienie problemu zaczyna się na stronie 105.

Jest tutaj kolejna interesująca dyskusja (po Good Parts) , która obejmuje tę propozycję:

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

Cała dyskusja sprawia, że ​​nigdy nie chcę wiedzieć, czy coś jest tablicą.

Nosredna
źródło
1
spowoduje to uszkodzenie w IE dla obiektów typu string i wyklucza łańcuchy podstawowe, które są podobne do tablic z wyjątkiem IE; zaznaczenie [[Class]] jest lepsze, jeśli chcesz rzeczywistych tablic; jeśli chcesz obiekty podobne do tablic, kontrola jest zbyt restrykcyjna
Christoph
@ Christoph - dodałem trochę więcej poprzez edycję. Fascynujący temat.
Nosredna
2

jQuery implementuje funkcję isArray, która sugeruje najlepszy sposób na zrobienie tego

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

(fragment zaczerpnięty z jQuery v1.3.2 - lekko dostosowany, aby mieć sens poza kontekstem)

Mario Menger
źródło
Zwracają false na IE (# 2968). (Ze źródła jquery)
zniszczony
1
Ten komentarz w źródle jQuery wydaje się odnosić do funkcji isFunction, a nie do isArray
Mario Menger
4
powinieneś użyć Object.prototype.toString()- jest mniej prawdopodobne, że się zepsuje
Christoph
2

Kradzież od guru Johna Resiga i jQuery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
zrównany
źródło
2
Drugi test również zwróciłby wartość true dla ciągu znaków: typeof "abc" .length === "number" // true
Daniel Vandersluis
2
Poza tym wydaje mi się, że nigdy nie powinno się zakodować na stałe nazw typów, takich jak „liczba”. Zamiast tego spróbuj porównać to z rzeczywistą liczbą, na przykład typeof (obj) == typeof (42)
ohnoes
5
@mtod: dlaczego nazwy typów nie powinny być zakodowane na stałe? w końcu zwracane wartości typeofsą znormalizowane?
Christoph
1
@ohnoes wyjaśnij sobie
Pacerier,
1

Co zamierzasz zrobić z wartością, gdy zdecydujesz, że jest to tablica?

Na przykład, jeśli zamierzasz wyliczyć zawarte wartości, jeśli wygląda to jak tablica LUB jeśli jest to obiekt używany jako tabela skrótów, to poniższy kod pobierze to, czego chcesz (ten kod zatrzymuje się, gdy funkcja zamykająca zwraca cokolwiek innego niż „niezdefiniowany”. Należy pamiętać, że NIE wykonuje iteracji po kontenerach COM ani wyliczeniach; jest to pozostawione jako ćwiczenie dla czytelnika):

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

(Uwaga: testy „o! = Null” zarówno dla wartości null, jak i undefined)

Przykłady użycia:

// 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
źródło
chociaż odpowiedź może być interesująca, tak naprawdę nie ma to nic wspólnego z pytaniem (a przy okazji: iterowanie po tablicach przez for..injest złe [tm])
Christoph
@Christoph - Jasne, że tak. Musi być jakiś powód, aby zdecydować, czy coś jest tablicą: ponieważ chcesz coś zrobić z wartościami. Najbardziej typowe rzeczy (przynajmniej w moim kodzie) to mapowanie, filtrowanie, wyszukiwanie lub inne przekształcanie danych w tablicy. Powyższa funkcja robi dokładnie to: jeśli przekazana rzecz ma elementy, które mogą być iterowane, to dokonuje iteracji po nich. Jeśli nie, to nic nie robi i nieszkodliwie zwraca undefined.
James Hugard
@Christoph - Dlaczego iteracja po tablicach z for..in bad [tm]? Jak inaczej mógłbyś iterować po tablicach i / lub tablicach mieszających (obiektach)?
James Hugard
1
@James: for..initeruje po niezliczonych właściwościach obiektów; nie powinieneś używać go z tablicami, ponieważ: (1) jest wolny; (2) nie gwarantuje zachowania porządku; (3) będzie zawierać dowolną właściwość zdefiniowaną przez użytkownika ustawioną w obiekcie lub którykolwiek z jego prototypów, ponieważ ES3 nie zawiera żadnego sposobu na ustawienie atrybutu DontEnum; mogą być inne problemy, które wypadły mi z głowy
Christoph
1
@Christoph - Z drugiej strony użycie for (;;) nie będzie działać poprawnie w przypadku rzadkich tablic i nie będzie iterować właściwości obiektu. # 3 jest uważany za zły format Object z powodu, o którym wspomniałeś. Z drugiej strony, masz rację TAK, jeśli chodzi o wydajność: for..in jest ~ 36x wolniejsze niż for (;;) w 1M tablicy elementów. Łał. Niestety, nie ma zastosowania do naszego głównego przypadku użycia, czyli iteracji właściwości obiektów (tablic mieszających).
James Hugard
1

Jeśli robisz to w CouchDB (SpiderMonkey), użyj

Array.isArray(array)

jak array.constructor === Arraylub array instanceof Arraynie działają. Używanie array.toString() === "[object Array]"działa, ale w porównaniu z nim wydaje się dość podejrzane.

Daniel Worthington-Bodart
źródło
Działa to w Node.js i również w przeglądarkach, nie tylko CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
Karl Wilbur
0

Jeśli chcesz korzystać z różnych przeglądarek, potrzebujesz jQuery.isArray .

Otto Allmendinger
źródło
0

Na w3school jest przykład, który powinien być dość standardowy.

Aby sprawdzić, czy zmienna jest tablicą, używają czegoś podobnego do tego

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

testowane na Chrome, Firefox, Safari, ie7

Eineki
źródło
używanie constructordo sprawdzania typu jest zbyt kruche; zamiast tego użyj jednej z sugerowanych alternatyw
Christoph
dlaczego tak myślisz? O kruchej?
Kamarey
@Kamarey: constructorto zwykła właściwość DontEnum obiektu prototypowego; może to nie stanowić problemu w przypadku typów wbudowanych, o ile nikt nie robi nic głupiego, ale w przypadku typów zdefiniowanych przez użytkownika jest to łatwe; moja rada: zawsze używaj instanceof, co sprawdza łańcuch prototypów i nie polega na właściwościach, które można dowolnie nadpisać
Christoph
1
Dzięki, znalazłem inne wyjaśnienie tutaj: thinkweb2.com/projects/prototype/…
Kamarey
Nie jest to wiarygodne, ponieważ sam obiekt Array można zastąpić obiektem niestandardowym.
Josh Stodola
-2

Jedną z najlepiej zbadanych i omawianych wersji tej funkcji można znaleźć na stronie PHPJS . Możesz utworzyć link do pakietów lub przejść bezpośrednio do funkcji . Gorąco polecam stronę za dobrze skonstruowane odpowiedniki funkcji PHP w JavaScript.

Tony Miller
źródło
-2

Za mało odwołań równych konstruktorom. Czasami mają różne referencje konstruktora. Dlatego używam ich reprezentacji w postaci ciągów.

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}
kuy
źródło
oszukany przez{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi
-4

Wymień Array.isArray(obj)naobj.constructor==Array

próbki:

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

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

patrick72
źródło
3
Dlaczego miałbyś zastąpić okropną funkcję, aby poprawić jej działanie?
Bergi