Як визначити, чи є змінною масив


101

Який найкращий де-факто стандартний крос-браузерний метод для визначення того, чи є змінна в JavaScript масив чи ні?

Шукаючи в Інтернеті є безліч різних пропозицій, кілька хороших і досить багато недійсних.

Наприклад, базовий підхід:

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

Однак зауважте, що станеться, якщо масив порожній, або obj насправді не є масивом, але реалізує властивість довжини тощо.

Отже, яка реалізація є найкращою з точки зору того, як реально працювати, будучи крос-браузером і все ще ефективно працювати?


4
Чи не буде це поверненням істинним у рядку?
Джеймс Х'югард

Наведений приклад - це не ментант для відповіді на саме запитання, це лише приклад того, як можна підходити до рішення - яке часто не вдається в особливих випадках (як, наприклад, "Однак, зверніть увагу ...").
stpe

@James: у більшості браузерів (виключено IE) рядки схожі на масив (тобто доступ через числові індекси можливий)
Крістоф

1
не можу повірити, що це так важко зробити ...
Клавдіу

Відповіді:


160

Перевірка типу об’єктів у JS проводиться за допомогою instanceof, тобто

obj instanceof Array

Це не спрацює, якщо об’єкт передається через межі кадру, оскільки кожен кадр має власний Arrayоб'єкт. Ви можете обійти це, перевіривши внутрішню властивість [[Class]] об'єкта. Щоб отримати його, використовуйте Object.prototype.toString()(це гарантовано працює ECMA-262):

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

Обидва методи працюватимуть лише для фактичних масивів, а не для об’єктів, схожих на масив, таких як список argumentsоб'єктів чи вузлів. Оскільки всі об’єкти, схожі на масив, повинні мати числове lengthвластивість, я би перевірив наявність таких:

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

Зверніть увагу, що рядки пройдуть цю перевірку, що може призвести до проблем, оскільки IE не дозволяє отримати доступ до символів рядка за індексом. Тому ви можете змінити, typeof obj !== 'undefined'щоб typeof obj === 'object'виключити примітиви та об'єкти хосту з типами, відмінними від усіх 'object'. Це все ще дозволить пропускати рядкові об'єкти, які доведеться виключити вручну.

У більшості випадків ви насправді хочете знати, чи можете ви перебирати об’єкт за допомогою числових індексів. Тому може бути корисним перевірити, чи має об’єкт 0замість нього властивість , що можна зробити за допомогою однієї з таких перевірок:

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

Передача об'єкту необхідна для коректної роботи масивів, подібних до примітивів (тобто рядків).

Ось код надійної перевірки масивів JS:

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

і ітерабельні (тобто не порожні) масиви-подібні об'єкти:

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-матриці перевірки.
Носредна

Станом на MS JS 5.6 (IE6?), Оператор "instanceof" просочив багато пам'яті під час запуску проти об'єкта COM (ActiveXObject). Не перевіряли JS 5.7 або JS 5.8, але це все ще може бути справедливим.
Джеймс Хагард

1
@James: цікаво - я не знав про цей витік; у будь-якому випадку, є легке виправлення: в IE hasOwnPropertyметод має лише метод власних об'єктів JS , тож просто приставте свій файл instanceofдо obj.hasOwnProperty && ; Крім того, це все ще проблема з IE7? мої прості тести за допомогою диспетчера завдань свідчать про те, що пам’ять повертається після мінімізації браузера ...
Крістоф

@Christoph - Не впевнений у IE7, але IIRC цього не було у списку помилок для JS 5.7 або 5.8. Ми розміщуємо базовий двигун JS на стороні сервера в сервісі, тому мінімізація інтерфейсу користувача не застосовується.
Джеймс Хагард

1
@TravisJ: див. ECMA-262 5.1, розділ 15.2.4.2 ; Назви внутрішніх класів у верхньому регістрі - див. розділ 8.6.2
Крістоф

42

Прибуття ECMAScript 5th Edition дає нам найбільш впевнений метод тестування, якщо змінною є масив, Array.isArray () :

Array.isArray([]); // true

Хоча прийнята відповідь тут працюватиме в рамках кадрів і вікон для більшості браузерів, це не стосується Internet Explorer 7 і нижче , оскільки Object.prototype.toStringвиклик масиву з іншого вікна повернеться [object Object], ні [object Array]. IE 9, здається, також відмовився від такої поведінки (див. Оновлене виправлення нижче).

Якщо потрібно рішення, яке працює у всіх браузерах, ви можете скористатися:

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

Це не зовсім непорушно, але його зламає лише той, хто намагається зламати його. Він вирішує проблеми в IE7 та нижче та IE9. Помилка все ще існує в IE 10 PP2 , але вона може бути виправлена ​​до випуску.

PS, якщо ви не впевнені в рішенні, то рекомендую перевірити його на вміст серця та / або прочитати допис у блозі. Є й інші потенційні рішення, якщо вам незручно використовувати умовну компіляцію.


Прийнята відповідь добре працює в IE8 +, але не в IE6,7
користувач123444555621

@ Pumbaa80: Ви праві :-) IE8 виправляє проблему для власного Object.prototype.toString, але при тестуванні масиву, створеного в режимі документа IE8, який передається в режим IE7 або нижчий документ, проблема зберігається. Я лише протестував цей сценарій, а не навпаки. Я відредагував застосувати це виправлення лише до IE7 та новіших версій.
Енді Е

WAAAAAAA, я ненавиджу IE. Я зовсім забув про різні "режими документування" або "режими шизо", як я їх називатиму.
користувач123444555621

Хоча ідеї чудові, і це виглядає добре, мені це не спрацьовує в IE9 із спливаючими вікнами. Він повертає помилку для масивів, створених відкривачем ... Чи є сумісні рішення IE9? (Здається, проблема полягає в тому, що IE9 реалізує сам Array.isArray, який повертає помилку для даного випадку, коли цього не слід.
Steffen Heil

@Steffen: цікаво, тому нативна реалізація isArrayне повертає істину з масивів, створених в інших режимах документа? Мені доведеться розібратися в цьому, коли отримаю певний час, але я думаю, що найкраще зробити - це помилка на Connect, щоб її можна було виправити в IE 10.
Енді Е

8

У Крокфорда є дві відповіді на сторінці 106 "Хороших частин". Перший перевіряє конструктор, але видасть помилкові негативи для різних кадрів чи вікон. Ось другий:

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

Крокфорд вказує, що ця версія визначатиме argumentsмасив як масив, хоча у нього немає жодного з методів масиву.

Його цікаве обговорення проблеми починається на сторінці 105.

Існує одне цікаве обговорення (пост-Good Parts) тут , який включає в себе цю пропозицію:

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

В ході всієї дискусії я ніколи не хочу знати, чи є щось чи не масив.


1
це порушить IE для об'єктів рядків і виключає рядкові примітиви, схожі на масив, за винятком IE; перевірка [[Class]] краще, якщо ви хочете фактичних масивів; якщо ви хочете об'єкти, що подобаються масиву, чек є занадто обмежуючим
Крістоф

@ Крістоф - я додав трохи більше за допомогою редагування. Захоплююча тема.
Nosredna

2

jQuery реалізує функцію isArray, яка пропонує найкращий спосіб це зробити

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

(фрагмент, узятий з jQuery v1.3.2 - трохи відрегульований, щоб мати сенс поза контекстом)


Вони повертають помилку на IE (# 2968). (З джерела jquery)
розбита

1
Цей коментар у джерелі jQuery, здається, посилається на функцію isFunction, а не на Arrray
Маріо Менгер

4
ви повинні використовувати Object.prototype.toString()- це менше шансів зламати
Крістоф

2

Викрадення у гуру Джона Ресіга та джикері:

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

2
Другий тест також поверне істину для рядка: typeof "abc" .length === "number" // true
Daniel Vandersluis

2
Плюс, я думаю, вам ніколи не слід вводити жорсткі імена типу "число". Спробуйте порівняти його з фактичним числом, наприклад, typeof (obj) == typeof (42)
ohnoes

5
@mtod: чому не слід вводити імена жорстко? зрештою, повернені значення typeofстандартизовані?
Крістоф

1
@ohnoes пояснити себе
Pacerier

1

Що ти будеш робити зі значенням, коли вирішиш, що це масив?

Наприклад, якщо ви маєте намір перерахувати містяться значення, якщо це схоже на масив АБО, якщо це об'єкт, який використовується як хеш-таблиця, то наступний код отримує те, що ви хочете (цей код припиняється, коли функція закриття повертає все інше ніж "невизначений". Зауважте, що він НЕ ітерається над COM-контейнерами чи перерахуваннями; це залишається як вправа для читача):

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

(Примітка: тести "o! = Null" як для null, так і для undefined)

Приклади використання:

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

хоча відповідь може бути цікавою, вона насправді не має нічого спільного з питанням (і btw: ітерація над масивами через for..in- погана [tm])
Крістоф

@Christoph - Звичайно, це так. Має бути якась причина, щоб вирішити, чи щось є масив: тому що ви хочете щось зробити зі значеннями. Найбільш типові речі (як мінімум, у моєму коді) - це картографувати, фільтрувати, шукати чи іншим чином трансформувати дані в масиві. Вищевказана функція робить саме так: якщо передана річ має елементи, які можна повторити, то вона повторюється над ними. Якщо ні, то він нічого не робить і нешкідливо повертається невизначеним.
Джеймс Хагард

@Christoph - Чому ітерація над масивами з for..in bad [tm]? Як інакше ви б переглянули масиви та / або хештелі (об’єкти)?
Джеймс Хагард

1
@James: for..inповторює численні властивості об'єктів; не слід використовувати його з масивами, оскільки: (1) це повільно; (2) збереження порядку не гарантується; (3) він буде включати будь-яке визначене користувачем властивість, встановлене в об'єкті або будь-якому з його прототипів, оскільки ES3 не містить жодного способу встановлення атрибута DontEnum; Можуть виникнути й інші питання, які прослизають у мене
Крістоф

1
@Christoph - З іншого боку, використання для (;;) не працюватиме належним чином для розріджених масивів і не буде ітератувати властивості об'єкта. №3 вважається поганою формою для Object через причину, яку ви згадуєте. З іншого боку, ви маєте рацію щодо продуктивності: for..in на 36 разів повільніше, ніж для (;;) на масиві елементів 1M. Ого. На жаль, не застосовується до нашого основного випадку використання - ітераційних властивостей об'єкта (хештелів).
Джеймс Хагард

1

Якщо ви робите це в CouchDB (SpiderMonkey), тоді використовуйте

Array.isArray(array)

як array.constructor === Arrayабо array instanceof Arrayне працюють. Використання array.toString() === "[object Array]"працює, але у порівнянні з цим здається досить хиткою.


Це працює і в Node.js, і в браузерах, а не лише CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Карл Вільбур


0

На w3school є приклад, який повинен бути досить стандартним.

Щоб перевірити, чи є змінною масив, вони використовують щось подібне до цього

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

перевірено на Chrome, Firefox, Safari, тобто7


використання constructorдля перевірки типу imo занадто крихке; скористайтеся однією із запропонованих альтернатив замість цього
Крістоф

чому ти так думаєш? Про крихке?
Камарей

@Kamarey: constructorє регулярною властивістю DontEnum об'єкта-прототипу; це може не бути проблемою для вбудованих типів, поки ніхто не робить нічого дурного, але для визначених користувачем типів це легко бути; моя порада: завжди використовуйте instanceof, що перевіряє прототип ланцюга і не покладається на властивості, які можна перезаписати довільно
Крістоф

1
Дякуємо, знайшли тут ще одне пояснення: thinkweb2.com/projects/prototype/…
Камарей

Це не є надійним, оскільки сам об’єкт Array може бути перезаписаний із спеціальним об'єктом.
Джош Стодола

-2

Одну з найкращих досліджених та обговорюваних версій цієї функції можна знайти на сайті PHPJS . Ви можете зв’язатись з пакетами або безпосередньо перейти до функції . Я настійно рекомендую сайту добре побудувати еквіваленти функцій PHP в JavaScript.


-2

Недостатньо еталонних рівних конструкторів. Колись у них є різні посилання на конструктор. Тому я використовую рядкові їх зображення.

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

обдурив{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Бергі

-4

Замінити Array.isArray(obj)наobj.constructor==Array

зразки:

Array('44','55').constructor==Array повернути справжню (IE8 / Chrome)

'55'.constructor==Array повернути помилкове (IE8 / Chrome)


3
Чому б ви замінили правильну функцію жахливою?
Бергі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.