Bagaimana mendeteksi apakah suatu variabel adalah array

101

Apa metode lintas-browser standar de-facto terbaik untuk menentukan apakah variabel dalam JavaScript adalah larik atau bukan?

Mencari di web ada sejumlah saran berbeda, beberapa bagus dan beberapa tidak valid.

Misalnya, berikut ini adalah pendekatan dasar:

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

Namun, perhatikan apa yang terjadi jika array kosong, atau obj sebenarnya bukan array tetapi mengimplementasikan properti panjang, dll.

Jadi penerapan mana yang terbaik dalam hal benar-benar berfungsi, bersifat lintas-browser, dan masih berkinerja efisien?

stpe
sumber
4
Tidakkah ini mengembalikan true pada sebuah string?
James Hugard
Contoh yang diberikan tidak diberikan untuk menjawab pertanyaan itu sendiri, hanyalah sebuah contoh bagaimana sebuah solusi dapat didekati - yang seringkali gagal dalam kasus-kasus khusus (seperti yang satu ini, maka "Namun, perhatikan ...").
stpe
@James: di sebagian besar browser (tidak termasuk IE), string seperti array (yaitu akses melalui indeks numerik dimungkinkan)
Christoph
1
tidak percaya ini sangat sulit untuk dilakukan ...
Claudiu

Jawaban:

161

Pemeriksaan jenis objek di JS dilakukan melalui instanceof, yaitu

obj instanceof Array

Ini tidak akan berfungsi jika objek melewati batas bingkai karena setiap bingkai memiliki Arrayobjeknya sendiri . Anda bisa menyiasatinya dengan memeriksa properti internal [[Class]] dari objek. Untuk mendapatkannya, gunakan Object.prototype.toString()(ini dijamin bekerja oleh ECMA-262):

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

Kedua metode hanya akan bekerja untuk array yang sebenarnya dan bukan objek seperti array seperti argumentsobjek atau daftar node. Karena semua objek yang mirip array harus memiliki lengthproperti numerik , saya akan memeriksanya seperti ini:

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

Harap dicatat bahwa string akan melewati pemeriksaan ini, yang dapat menyebabkan masalah karena IE tidak mengizinkan akses ke karakter string dengan indeks. Oleh karena itu, Anda mungkin ingin mengubah typeof obj !== 'undefined'ke typeof obj === 'object'mengecualikan primitif dan benda-benda host dengan jenis yang berbeda dari 'object'alltogether. Ini akan tetap membiarkan objek string lewat, yang harus dikecualikan secara manual.

Dalam kebanyakan kasus, yang sebenarnya ingin Anda ketahui adalah apakah Anda dapat melakukan iterasi pada objek melalui indeks numerik. Oleh karena itu, sebaiknya periksa apakah objek tersebut memiliki properti bernama 0, yang bisa dilakukan melalui salah satu pemeriksaan berikut:

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

Cast to object diperlukan untuk bekerja dengan benar untuk primitif mirip-array (yaitu string).

Berikut kode untuk pemeriksaan kuat untuk array JS:

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

dan objek seperti array yang dapat diulang (yaitu tidak kosong):

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
sumber
7
Ringkasan bagus dari state-of-the-art dalam pemeriksaan array js.
Nosredna
Pada MS JS 5.6 (IE6?), Operator "instanceof" membocorkan banyak memori saat dijalankan terhadap objek COM (ActiveXObject). Belum memeriksa JS 5.7 atau JS 5.8, tapi ini mungkin masih berlaku.
James Hugard
1
@ James: menarik - Saya tidak tahu tentang kebocoran ini; bagaimanapun, ada perbaikan yang mudah: di IE, hanya objek JS asli yang memiliki hasOwnPropertymetode, jadi awali saja instanceofdengan obj.hasOwnProperty && ; juga, apakah ini masih menjadi masalah dengan IE7? pengujian sederhana saya melalui pengelola tugas menunjukkan bahwa memori diperoleh kembali setelah meminimalkan browser ...
Christoph
@Christoph - Tidak yakin tentang IE7, tetapi IIRC ini tidak ada dalam daftar perbaikan bug untuk JS 5.7 atau 5.8. Kami menghosting mesin JS yang mendasari di sisi server dalam suatu layanan, jadi meminimalkan UI tidak berlaku.
James Hugard
1
@TravisJ: lihat ECMA-262 5.1, bagian 15.2.4.2 ; nama kelas internal berdasarkan konvensi huruf besar - lihat bagian 8.6.2
Christoph
43

Kehadiran ECMAScript Edisi ke-5 memberi kita metode pengujian paling pasti jika variabel adalah array, Array.isArray () :

Array.isArray([]); // true

Meskipun jawaban yang diterima di sini akan berfungsi di seluruh bingkai dan jendela untuk sebagian besar browser, itu tidak untuk Internet Explorer 7 dan yang lebih rendah , karena Object.prototype.toStringdipanggil pada array dari jendela yang berbeda akan kembali [object Object], bukan [object Array]. IE 9 tampaknya juga telah mundur ke perilaku ini (lihat perbaikan yang diperbarui di bawah).

Jika Anda menginginkan solusi yang berfungsi di semua browser, Anda dapat menggunakan:

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

Ini tidak sepenuhnya tidak bisa dipecahkan, tetapi hanya akan dipatahkan oleh seseorang yang berusaha keras untuk memecahkannya. Ia bekerja di sekitar masalah di IE7 dan yang lebih rendah dan IE9. Bug masih ada di IE 10 PP2 , tetapi mungkin diperbaiki sebelum rilis.

NB, jika Anda tidak yakin tentang solusinya maka saya sarankan Anda mengujinya sesuka hati dan / atau membaca posting blog. Ada solusi potensial lain di sana jika Anda tidak nyaman menggunakan kompilasi bersyarat.

Andy E
sumber
Jawaban yang diterima berfungsi dengan baik di IE8 +, tetapi tidak di IE6,7
pengguna123444555621
@ Pumbaa80: Anda benar :-) IE8 memperbaiki masalah untuk Object.prototype.toString-nya sendiri, tetapi menguji larik yang dibuat dalam mode dokumen IE8 yang diteruskan ke mode dokumen IE7 atau yang lebih rendah, masalah tetap ada. Saya hanya menguji skenario ini dan bukan sebaliknya. Saya telah mengedit untuk menerapkan perbaikan ini hanya untuk IE7 dan yang lebih rendah.
Andy E
WAAAAAAA, saya benci IE. Saya benar-benar lupa tentang "mode dokumen" yang berbeda, atau "mode schizo", begitu saya menyebutnya.
pengguna123444555621
Meskipun idenya bagus dan terlihat bagus, ini tidak berhasil untuk saya di IE9 dengan jendela popup. Ia mengembalikan false untuk array yang dibuat oleh pembuka ... Apakah ada solusi compatbile IE9? (Masalahnya tampaknya IE9 mengimplementasikan Array.isArray itu sendiri, yang mengembalikan false untuk kasus yang diberikan, padahal seharusnya tidak.
Steffen Heil
@ Steffen: menarik, jadi implementasi asli isArraytidak mengembalikan true dari array yang dibuat dalam mode dokumen lain? Saya harus memeriksanya ketika saya punya waktu, tetapi menurut saya hal terbaik untuk dilakukan adalah melaporkan bug di Connect sehingga dapat diperbaiki di IE 10.
Andy E
8

Crockford memiliki dua jawaban di halaman 106 dari "The Good Parts." Yang pertama memeriksa konstruktor, tetapi akan memberikan negatif palsu di seluruh bingkai atau jendela yang berbeda. Ini yang kedua:

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

Crockford menunjukkan bahwa versi ini akan mengidentifikasi argumentsarray sebagai array, meskipun tidak memiliki metode array apa pun.

Pembahasannya yang menarik tentang masalah ini dimulai pada halaman 105.

Ada diskusi menarik lebih lanjut (post-Good Parts) di sini yang meliputi proposal ini:

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

Semua diskusi membuat saya tidak pernah ingin tahu apakah sesuatu itu array atau tidak.

Nosredna
sumber
1
ini akan istirahat di IE untuk objek string dan mengecualikan string primitif, yang mirip array kecuali di IE; memeriksa [[Class]] lebih baik jika Anda menginginkan array yang sebenarnya; jika Anda ingin objek yang menyukai array, pemeriksaan ini terlalu ketat
Christoph
@ Christoph - Saya menambahkan sedikit lebih banyak melalui edit. Topik yang menarik.
Nosredna
2

jQuery mengimplementasikan fungsi isArray, yang menyarankan cara terbaik untuk melakukannya adalah

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

(cuplikan diambil dari jQuery v1.3.2 - sedikit disesuaikan agar masuk akal di luar konteks)

Mario Menger
sumber
Mereka mengembalikan false di IE (# 2968). (Dari sumber jquery)
dihancurkan
1
Komentar itu di sumber jQuery tampaknya merujuk pada fungsi isFungsi, bukan isArray
Mario Menger
4
Anda harus menggunakan Object.prototype.toString()- yang cenderung tidak rusak
Christoph
2

Mencuri dari guru John Resig dan jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}
dihancurkan
sumber
2
Tes kedua akan mengembalikan true untuk string juga: typeof "abc" .length === "number" // true
Daniel Vandersluis
2
Plus, saya rasa Anda tidak boleh mengetik nama hardcode, seperti "angka". Coba bandingkan dengan angka sebenarnya, seperti typeof (obj) == typeof (42)
ohnoes
5
@mtod: mengapa nama jenis tidak harus di-hardcode? setelah semua, nilai kembalian typeofdistandarisasi?
Christoph
1
@ Johnoes menjelaskan diri Anda sendiri
Pacerier
1

Apa yang akan Anda lakukan dengan nilai setelah Anda memutuskan itu adalah array?

Misalnya, jika Anda bermaksud untuk menghitung nilai yang terkandung jika terlihat seperti larik ATAU jika itu adalah objek yang digunakan sebagai tabel hash, maka kode berikut mendapatkan apa yang Anda inginkan (kode ini berhenti ketika fungsi penutupan mengembalikan apa pun yang lain dari "undefined". Perhatikan bahwa ini TIDAK mengulang kontainer atau enumerasi COM; yang tersisa sebagai latihan untuk pembaca):

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

(Catatan: pengujian "o! = Null" untuk null & undefined)

Contoh penggunaan:

// 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
sumber
meskipun jawabannya mungkin menarik, itu tidak benar-benar ada hubungannya dengan pertanyaan (dan btw: iterasi atas array melalui for..inburuk [tm])
Christoph
@ Christoph - Tentu saja. Pasti ada beberapa alasan untuk memutuskan apakah sesuatu itu Array: karena Anda ingin melakukan sesuatu dengan nilai. Hal yang paling umum (setidaknya dalam kode saya) adalah memetakan, memfilter, mencari, atau mengubah data dalam array. Fungsi di atas melakukan persis seperti itu: jika benda yang diteruskan memiliki elemen yang dapat diulangi, maka ia akan mengulanginya. Jika tidak, maka itu tidak melakukan apa-apa dan mengembalikan tanpa batas.
James Hugard
@Christoph - Mengapa melakukan iterasi pada array dengan for..in bad [tm]? Bagaimana lagi Anda akan mengulang array dan / atau hashtable (objek)?
James Hugard
1
@James: for..inmengulangi properti objek yang tak terhitung banyaknya ; Anda tidak boleh menggunakannya dengan array karena: (1) lambat; (2) tidak ada jaminan untuk menjaga ketertiban; (3) ini akan menyertakan setiap properti yang ditentukan pengguna yang ditetapkan dalam objek atau prototipe apa pun karena ES3 tidak menyertakan cara apa pun untuk menyetel atribut DontEnum; mungkin ada masalah lain yang terlintas di benak saya
Christoph
1
@Christoph - Di sisi lain, menggunakan for (;;) tidak akan berfungsi dengan baik untuk array jarang dan tidak akan mengulang properti objek. # 3 dianggap bentuk yang buruk untuk Object karena alasan yang kamu sebutkan. Di sisi lain, Anda benar terkait dengan kinerja: for..in ~ 36x lebih lambat daripada untuk (;;) pada larik elemen 1M. Wow. Sayangnya, tidak berlaku untuk kasus penggunaan utama kami, yaitu mengiterasi properti objek (hashtables).
James Hugard
1

Jika Anda melakukan ini di CouchDB (SpiderMonkey), gunakan

Array.isArray(array)

sebagai array.constructor === Arrayatau array instanceof Arraytidak bekerja. Menggunakan array.toString() === "[object Array]"memang berhasil tetapi tampaknya cukup cerdik jika dibandingkan.

Daniel Worthington-Bodart
sumber
Ini berfungsi di Node.js dan juga di browser, bukan hanya CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur
0

Jika Anda ingin lintas-browser, Anda menginginkan jQuery.isArray .

Otto Allmendinger
sumber
0

Di w3school ada contoh yang seharusnya cukup standar.

Untuk memeriksa apakah suatu variabel adalah array mereka menggunakan sesuatu yang mirip dengan ini

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

diuji di Chrome, Firefox, Safari, ie7

Eineki
sumber
menggunakan constructoruntuk pemeriksaan tipe imo terlalu rapuh; gunakan salah satu alternatif yang disarankan
Christoph
kenapa menurutmu begitu? Tentang rapuh?
Kamarey
@Kamarey: constructoradalah properti DontEnum biasa dari objek prototipe; ini mungkin tidak menjadi masalah untuk tipe built-in selama tidak ada yang melakukan sesuatu yang bodoh, tapi untuk tipe yang ditentukan pengguna bisa dengan mudah; saran saya: selalu gunakan instanceof, yang memeriksa rantai prototipe dan tidak bergantung pada properti yang dapat ditimpa secara sewenang
Christoph
1
Terima kasih, temukan penjelasan lain di sini: thinkweb2.com/projects/prototype/…
Kamarey
Ini tidak dapat diandalkan, karena objek Array itu sendiri dapat ditimpa dengan objek kustom.
Josh Stodola
-2

Salah satu versi terbaik yang diteliti dan didiskusikan dari fungsi ini dapat ditemukan di situs PHPJS . Anda dapat menautkan ke paket atau Anda dapat langsung membuka fungsinya . Saya sangat merekomendasikan situs ini untuk konstruksi yang setara dengan fungsi PHP di JavaScript.

Tony Miller
sumber
-2

Referensi tidak cukup sama dengan konstruktor. Terkadang mereka memiliki referensi konstruktor yang berbeda. Jadi saya menggunakan representasi string dari mereka.

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

Gantikan Array.isArray(obj)denganobj.constructor==Array

sampel:

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

'55'.constructor==Array kembali salah (IE8 / Chrome)

patrick72
sumber
3
Mengapa Anda mengganti ke fungsi yang benar dengan yang mengerikan?
Bergi