Как определить, является ли переменная массивом

102

Каков наилучший де-факто стандартный кроссбраузерный метод определения того, является ли переменная в JavaScript массивом или нет?

При поиске в Интернете можно найти множество различных предложений, некоторые из которых хороши, а некоторые нет.

Например, следующий базовый подход:

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

Однако обратите внимание, что происходит, если массив пуст или obj на самом деле не массив, а реализует свойство длины и т. Д.

Итак, какая реализация является лучшей с точки зрения фактической работы, кроссбраузерности и эффективности?

stpe
источник
5
Разве это не вернется к строке?
Джеймс Хугард,
Приведенный пример не предназначен для ответа на сам вопрос, это просто пример того, как можно подойти к решению, которое часто терпит неудачу в особых случаях (например, этот, отсюда «Однако, примечание ...»).
Сб
@James: в большинстве браузеров (за исключением IE) строки похожи на массивы (т.е. возможен доступ через числовые индексы)
Кристоф
1
Не могу поверить, что это так сложно сделать ...
Клаудиу

Ответы:

162

Проверка типов объектов в 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.
Nosredna
Начиная с MS JS 5.6 (IE6?), Оператор instanceof приводит к утечке большого количества памяти при работе с COM-объектом (ActiveXObject). Не проверяли JS 5.7 или JS 5.8, но это все еще может быть правдой.
Джеймс Хугард,
1
@ Джеймс: интересно - я не знал об этой утечке; во всяком случае, легко исправить: в IE, только родные JS объекты имеют hasOwnPropertyметод, так просто префикс для ваших 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
Christoph
43

Появление 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
user123444555621,
@ Pumbaa80: Вы правы :-) IE8 устраняет проблему для своего собственного Object.prototype.toString, но при тестировании массива, созданного в режиме документа IE8, который передается в режим документа IE7 или более ранней версии, проблема сохраняется. Я тестировал только этот сценарий, а не наоборот. Я отредактировал, чтобы применить это исправление только к IE7 и ниже.
Энди Э
ВААААААА, ненавижу IE. Я совершенно забыл о различных «режимах документа» или «режимах шизо», как я собираюсь их называть.
user123444555621
Хотя идея прекрасна и выглядит хорошо, у меня это не работает в IE9 со всплывающими окнами. Он возвращает false для массивов, созданных открывателем ... Есть ли совместимые с IE9 решения? (Проблема, похоже, в том, что IE9 реализует сам Array.isArray, который возвращает false для данного случая, хотя этого не должно быть.
Steffen Heil
@Steffen: интересно, значит, собственная реализация isArrayне возвращает true из массивов, созданных в других режимах документа? Мне придется разобраться с этим, когда у меня будет время, но я думаю, что лучше всего зарегистрировать ошибку в 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]] лучше, если вам нужны реальные массивы; если вам нужны объекты, похожие на массивы, проверка будет слишком строгой
Кристоф
@ Christoph - Я добавил немного больше через редактирование. Увлекательная тема.
Nosredna
2

jQuery реализует функцию isArray, которая предлагает лучший способ сделать это -

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

(фрагмент взят из jQuery v1.3.2 - немного скорректирован, чтобы иметь смысл вне контекста)

Марио Менгер
источник
Они возвращают false в IE (# 2968). (Из источника jquery)
razzed
1
Этот комментарий в источнике jQuery, похоже, относится к функции isFunction, а не к isArray
Марио Менгер
4
вам следует использовать Object.prototype.toString()- вероятность того, что сломается, меньше
Christoph
2

Похищение у гуру Джона Ресига и jquery:

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
Даниэль Вандерслуис,
2
Кроме того, я думаю, вам никогда не следует жестко кодировать имена типов, такие как «число». Вместо этого попробуйте сравнить его с фактическим числом, например typeof (obj) == typeof (42)
ohnoes
5
@mtod: почему не следует жестко задавать имена типов? в конце концов, возвращаемые значения typeofстандартизированы?
Christoph
1
@ohnoes объяснись
Pacerier
1

Что вы собираетесь делать со значением, когда решите, что это массив?

Например, если вы намереваетесь перечислить содержащиеся значения, если он выглядит как массив ИЛИ, если это объект, используемый в качестве хеш-таблицы, то следующий код получает то, что вы хотите (этот код останавливается, когда функция закрытия возвращает что-либо другое чем "undefined". Обратите внимание, что он НЕ выполняет итерацию по контейнерам 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] );
        }
    })
}
Джеймс Хугард
источник
хотя ответ может быть интересным, на самом деле он не имеет ничего общего с вопросом (и, кстати, повторение массивов через for..in- это плохо [tm])
Кристоф
@Christoph - Конечно. Должна быть какая-то причина для решения, является ли что-то массивом: потому что вы хотите что-то сделать со значениями. Наиболее типичными вещами (по крайней мере, в моем коде) являются отображение, фильтрация, поиск или иное преобразование данных в массиве. Вышеупомянутая функция делает именно это: если переданная вещь имеет элементы, которые можно повторять, она выполняет итерацию по ним. Если нет, то он ничего не делает и безвредно возвращает undefined.
Джеймс Хугард,
@Christoph - Почему перебор массивов выполняется с помощью for..in bad [tm]? Как еще вы перебираете массивы и / или хэш-таблицы (объекты)?
Джеймс Хугард,
1
@James: for..inперебирает перечислимые свойства объектов; вы не должны использовать его с массивами, потому что: (1) он медленный; (2) сохранение порядка не гарантируется; (3) он будет включать любое определяемое пользователем свойство, установленное в объекте или любом из его прототипов, поскольку ES3 не включает никакого способа установки атрибута DontEnum; могут быть и другие проблемы, о которых я не думал
Кристоф
1
@Christoph - С другой стороны, использование for (;;) не будет работать должным образом для разреженных массивов и не будет повторять свойства объекта. # 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, IE7

Эйнеки
источник
использование constructorдля проверки типов слишком хрупко; используйте вместо этого одну из предложенных альтернатив
Christoph
Почему вы так думаете? О хрупком?
Kamarey
@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 вернуть false (IE8 / Chrome)

Патрик72
источник
3
Зачем вам заменять правильное функционирование на ужасное?
Берги