Bir değişkenin bir dizi olup olmadığı nasıl tespit edilir


102

JavaScript'teki bir değişkenin bir dizi olup olmadığını belirlemek için en iyi fiili standart çapraz tarayıcı yöntemi nedir?

İnternette arama yaparken, bir dizi farklı öneri vardır, bazıları iyi ve bazıları geçersizdir.

Örneğin, aşağıdaki temel bir yaklaşımdır:

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

Bununla birlikte, dizi boşsa veya obj aslında bir dizi değilse, ancak bir uzunluk özelliği uygularsa, ne olacağını unutmayın.

Peki, gerçekten çalışma, tarayıcılar arası olma ve yine de verimli performans açısından en iyi uygulama hangisidir?


5
Bu bir dizede doğru dönmez mi?
James Hugard

Verilen örnek, sorunun kendisini yanıtlamak için değil, yalnızca bir çözüme nasıl yaklaşılabileceğinin bir örneğidir - bu genellikle özel durumlarda başarısız olur (bunun gibi, "Ancak, not ...").
stpe

@James: Çoğu tarayıcıda (IE hariç), dizeler dizi gibidir (yani sayısal indeksler aracılığıyla erişim mümkündür)
Christoph

1
bunu yapmanın bu kadar zor olduğuna inanamıyorum ...
Claudiu

Yanıtlar:


162

JS'deki nesnelerin tür kontrolü instanceof, örn.

obj instanceof Array

Her karenin kendi Arraynesnesi olduğundan, nesne kare sınırlarının ötesine geçirilirse bu çalışmaz . Nesnenin dahili [[Class]] özelliğini kontrol ederek bu sorunu çözebilirsiniz . Bunu elde etmek için kullanın Object.prototype.toString()(bunun ECMA-262 tarafından çalışması garantilidir):

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

Her iki yöntem de yalnızca gerçek diziler için çalışır ve argumentsnesne veya düğüm listeleri gibi dizi benzeri nesneler için çalışmaz . Dizi benzeri tüm nesnelerin sayısal bir lengthözelliği olması gerektiğinden, bunları şu şekilde kontrol ederim:

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

Dizelerin bu denetimi geçeceğini ve IE'nin dizinin karakterlerine dizine göre erişime izin vermemesi nedeniyle sorunlara yol açabileceğini lütfen unutmayın. Bu nedenle, değiştirmek isteyebilirsiniz typeof obj !== 'undefined'üzere typeof obj === 'object'farklı türleri ile ilkeller ve ana nesneleri dışlamak için 'object'alltogether. Bu, yine de manuel olarak dışarıda bırakılması gereken dize nesnelerinin geçmesine izin verecektir.

Çoğu durumda, aslında bilmek istediğiniz şey, sayısal indeksler aracılığıyla nesne üzerinde yineleme yapıp yapamayacağınızdır. Bu nedenle, nesnenin 0bunun yerine adlandırılmış bir özelliğe sahip olup olmadığını kontrol etmek iyi bir fikir olabilir, bu şu kontrollerden biriyle yapılabilir:

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

Nesneye çevrim, dizi benzeri ilkellerin (yani dizelerin) doğru çalışması için gereklidir.

JS dizileri için sağlam denetimler için kod:

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

ve yinelenebilir (yani boş olmayan) dizi benzeri nesneler:

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
Js dizi denetimindeki en son teknolojinin harika bir özeti.
Nosredna

MS JS 5.6'dan (IE6?) İtibaren, "instanceof" operatörü bir COM nesnesine (ActiveXObject) karşı çalıştırıldığında çok fazla bellek sızdırdı. JS 5.7 veya JS 5.8'i kontrol etmediniz, ancak bu yine de geçerli olabilir.
James Hugard

1
@James: ilginç - Bu sızıntıyı bilmiyordum; her neyse, kolay bir düzeltme var: IE'de yalnızca yerel JS nesnelerinin bir hasOwnPropertyyöntemi vardır, bu nedenle yalnızca ön ekini instanceofile obj.hasOwnProperty && ; ayrıca, bu hala IE7 ile ilgili bir sorun mu? Görev yöneticisi aracılığıyla yaptığım basit testler, tarayıcıyı küçülttükten sonra belleğin geri kazanıldığını gösteriyor ...
Christoph

@Christoph - IE7 hakkında emin değilim, ancak IIRC bu JS 5.7 veya 5.8 için hata düzeltmeleri listesinde yoktu. Temel JS motorunu bir hizmette sunucu tarafında barındırıyoruz, bu nedenle kullanıcı arayüzünü en aza indirmek geçerli değildir.
James Hugard

1
@TravisJ: bkz. ECMA-262 5.1, bölüm 15.2.4.2 ; dahili sınıf isimleri geleneksel olarak büyük harftir
Christoph

43

ECMAScript 5. Sürümün gelişi , bir değişken bir dizi ise, Array.isArray () olarak bize en kesin test yöntemini verir :

Array.isArray([]); // true

Burada kabul edilen cevap, çoğu tarayıcı için çerçeveler ve pencereler arasında çalışacak olsa da, Internet Explorer 7 ve daha önceki sürümler için geçerli değildir , çünkü Object.prototype.toStringfarklı bir pencereden bir dizide çağrılanlar geri dönmez [object Object], dönmez [object Array]. IE 9 da bu davranışa gerilemiş görünüyor (aşağıdaki güncellenmiş düzeltmeye bakın).

Tüm tarayıcılarda çalışan bir çözüm istiyorsanız, şunları kullanabilirsiniz:

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

Tamamen kırılmaz değil, ancak yalnızca onu kırmaya çalışan biri tarafından kırılabilir. IE7 ve daha düşük ve IE9'daki sorunlar etrafında çalışır. Hata IE 10 PP2'de hala mevcuttur , ancak yayınlanmadan önce düzeltilebilir.

Not: Eğer çözümden emin değilseniz, bunu kalp içeriğinize göre test etmenizi ve / veya blog yazısını okumanızı tavsiye ederim. Koşullu derlemeyi kullanmaktan rahatsızsanız, orada başka olası çözümler de var.


Kabul edilen cevap IE8 + 'da iyi çalışıyor, ancak IE6,7'de değil
user123444555621

@ Pumbaa80: Haklısınız :-) IE8 sorunu kendi Object.prototype.toString için çözüyor, ancak IE8 belge modunda oluşturulan ve IE7 veya daha düşük bir belge moduna geçirilen bir diziyi test ederken sorun devam ediyor. Ben sadece bu senaryoyu test ettim, tersini değil. Bu düzeltmeyi yalnızca IE7 ve daha düşük sürümlere uygulamak için düzenledim.
Andy E

WAAAAAAA, IE'den nefret ediyorum. Farklı "belge modları" ya da "şizo modları" nı tamamen unutmuşum, onları adlandıracağım gibi.
user123444555621

Fikirler harika ve iyi görünse de, bu benim için açılır pencereli IE9'da çalışmıyor. Açıcı tarafından oluşturulan diziler için false döndürür ... IE9 uyumlu çözümler var mı? . (Sorun IE9 implemnets, ne zaman olmamalı verilen durum için yanlış döndüren kendisini Array.isArray o gibi görünüyor
Steffen Heil

@Steffen: ilginç, yani yerel uygulaması isArraydiğer belge modlarında oluşturulan dizilerden true döndürmüyor? Biraz zamanım olduğunda buna bakmam gerekecek, ancak bence yapılacak en iyi şey Connect'te bir hata dosyalamaktır, böylece IE 10'da düzeltilebilir.
Andy E

8

Crockford'un "İyi Parçalar" ın 106. sayfasında iki cevabı var. İlki kurucuyu kontrol eder, ancak farklı çerçevelerde veya pencerelerde yanlış negatifler verir. İşte ikincisi:

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

Crockford arguments, herhangi bir dizi yöntemine sahip olmasa da, bu sürümün diziyi bir dizi olarak tanımlayacağına işaret ediyor .

Sorunla ilgili ilginç tartışması 105. sayfada başlıyor.

Burada, bu öneriyi içeren başka ilginç tartışma (Post-Good Parts) var :

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

Tüm tartışmalar, bir şeyin bir dizi olup olmadığını bilmek istememe neden oluyor.


1
bu, dizge nesneleri için IE'de kırılır ve IE dışında dizi benzeri olan dizge ilkellerini hariç tutar; gerçek diziler istiyorsanız [[Sınıf]] 'ı kontrol etmek daha iyidir; dizi benzeri nesneler istiyorsanız, kontrol çok kısıtlayıcıdır
Christoph

@ Christoph - Bir düzenleme yoluyla biraz daha ekledim. Büyüleyici konu.
Nosredna

2

jQuery, bunu yapmanın en iyi yolunu öneren bir isArray işlevi uygular.

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

(jQuery v1.3.2'den alınan pasaj - bağlam dışında anlamlı olması için biraz ayarlandı)


IE'de yanlış döndürürler (# 2968). (Jquery bir kaynaktan)
razzed

1
JQuery kaynağındaki bu yorum isArray değil, isFunction işlevine atıfta bulunuyor gibi görünüyor
Mario Menger

4
kullanmalısınız Object.prototype.toString()- kırılma olasılığı daha düşüktür
Christoph

2

Guru John Resig ve jquery'den çalmak:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}

2
İkinci test bir dizi için de doğru sonucunu döndürecektir: typeof "abc" .length === "sayı" // true
Daniel Vandersluis

2
Artı, "sayı" gibi tür adlarını asla kodlamamalısınız. Bunun yerine gerçek sayı ile karşılaştırmayı deneyin, örneğin typeof (obj) == typeof (42)
ohnoes

5
@mtod: tür adları neden kodlanmamalı? sonuçta, dönüş değerleri typeofstandartlaştırılmış mı?
Christoph

1
@ohnoes kendini açıkla
Pacerier

1

Bir dizi olduğuna karar verdiğinizde değerle ne yapacaksınız?

Örneğin, içerilen değerleri bir dizi gibi görünüyorsa VEYA karma tablo olarak kullanılan bir nesneyse , içerdiği değerleri numaralandırmayı planlıyorsanız , aşağıdaki kod istediğiniz şeyi alır (bu kod, kapatma işlevi başka bir şey döndürdüğünde durur) COM kapsayıcıları veya numaralandırmalar üzerinde yineleme yapmadığını unutmayın; bu, okuyucu için bir alıştırma olarak bırakılmıştır):

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

(Not: "o! = Null" hem boş hem de tanımsız için testler)

Kullanım örnekleri:

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

cevap ilginç olsa da, aslında soruyla bir ilgisi yok (ve btw: diziler üzerinden yineleme for..inyapmak kötü [tm])
Christoph

@Christoph - Elbette öyle. Bir şeyin Dizi olup olmadığına karar vermenin bir nedeni olmalı: çünkü değerlerle bir şeyler yapmak istiyorsunuz. En tipik şeyler (en azından benim kodumda) dizideki verileri eşlemek, filtrelemek, aramak veya başka bir şekilde dönüştürmektir. Yukarıdaki işlev tam olarak bunu yapar: eğer aktarılan şey üzerinde yinelenebilen öğelere sahipse, bunların üzerinde yinelenir. Değilse, hiçbir şey yapmaz ve zararsız bir şekilde tanımsız olarak döner.
James Hugard

@Christoph - Neden for..in kötü [tm] ile diziler üzerinde yineleme yapıyor? Diziler ve / veya hashtable'lar (nesneler) üzerinde başka nasıl yinelersiniz?
James Hugard

1
@James: for..innesnelerin numaralandırılabilir özelliklerini yineler; onu dizilerle kullanmamalısınız çünkü: (1) yavaştır; (2) düzeni korumak garanti edilmez; (3) ES3, DontEnum niteliğini ayarlamak için herhangi bir yol içermediğinden, nesnede veya prototiplerinde herhangi bir kullanıcı tanımlı özellik kümesini içerecektir; aklımdan geçen başka sorunlar da olabilir
Christoph

1
@Christoph - Öte yandan, for (;;) kullanmak seyrek diziler için düzgün çalışmaz ve nesne özelliklerini yinelemez. # 3, bahsettiğiniz nedenden dolayı Object için kötü bir form olarak kabul edilir. Öte yandan, performans konusunda çok haklısınız: for..in, 1M eleman dizisinde for (;;) 'dan ~ 36 kat daha yavaştır. Vay. Ne yazık ki, nesne özelliklerini (hashtables) yineleyen ana kullanım durumumuz için geçerli değil.
James Hugard



0

Açık W3School oldukça standart olmalı bir örnek yoktur.

Bir değişkenin bir dizi olup olmadığını kontrol etmek için buna benzer bir şey kullanırlar

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

Chrome, Firefox, Safari, ie7 üzerinde test edildi


constructortür denetimi için kullanmak çok kırılgandır; bunun yerine önerilen alternatiflerden birini kullanın
Christoph

neden böyle düşünüyorsun? Kırılgan hakkında mı?
Kamarey

@Kamarey: constructorprototip nesnesinin normal bir DontEnum özelliğidir; Bu, kimse aptalca bir şey yapmadığı sürece yerleşik türler için bir sorun olmayabilir, ancak kullanıcı tanımlı türler için kolayca olabilir; tavsiyem: her zaman instanceofprototip zincirini kontrol eden ve keyfi olarak üzerine yazılabilen özelliklere güvenmeyen kullanın
Christoph

1
Teşekkürler, burada başka bir açıklama buldum: thinkweb2.com/projects/prototype/…
Kamarey

Bu güvenilir değildir, çünkü Array nesnesinin kendisi özel bir nesneyle üzerine yazılabilir.
Josh Stodola


-2

Oluşturuculara eşit yeterli referans yok. Bazen farklı kurucu referansları vardır. Bu yüzden onların dize temsillerini kullanıyorum.

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

kandırdı{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi

-4

Değiştir Array.isArray(obj)tarafındanobj.constructor==Array

örnekler:

Array('44','55').constructor==Array doğruya dön (IE8 / Chrome)

'55'.constructor==Array yanlış döndür (IE8 / Chrome)


3
Doğru işlevi neden korkunç bir işlevle değiştirdiniz?
Bergi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.