Как это ни странно, не всякий объект, имеющий числовые свойства и свойство length
является массивом. Не являются массивами, например, объект arguments
и коллекции возвращаемые DOM методами. Это означает, что у этих объектов нет методов, имеющихся у массивов, а также другое, не такое как у массивов, поведение при манипулировании числовыми свойствами и свойством length
.
function f() {
var a = [1, 2, 3];
alert(arguments.join); // undefined
alert([a.length, arguments.length]); // 3,3
alert([a[0] == arguments[0], a[1] == arguments[1], a[2] == arguments[2]]); // true,true,true
a.length = 0;
arguments.length = 0;
alert([a.length, arguments.length]); // 0,0
alert([a[0] == arguments[0], a[1] == arguments[1], a[2] == arguments[2]]); // false,false,false
alert(a); // пустое сообщение
alert([arguments[0], arguments[1], arguments[2]]); // 1,2,3
}
f(1, 2, 3);
Как же отличить массивы от других похожих объектов? Если интересующий вас объект может быть как массивом, так и любым другим объектом, а вам всего лишь нужно перебрать его элементы, то можно смело, без всяких проверок, перебирать элементы циклом for
от 0
до length - 1
. Если же нужно точно знать, имеется ли у нас массив или что-то другое, то будем проверять.
Самая логичная и, в принципе, самая правильная проверка — это instanceof
.
var a = [], s = '';
alert(a instanceof Array); // true
alert(s instanceof Array); // false
В большинстве случаев такой проверки будет достаточно, однако её нельзя назвать универсальной. В случае, если к вам придёт массив, созданный в другом окне/фрейме, то он не пройдёт проверку, т.к. прототип у этого массива будет другой.
Есть более надёжный и, что немаловажно, тоже компактный способ определения массива, как впрочем и любого другого native-объекта, — использование Object.prototype.toString
. Заглянем в спецификацию
When the toString method is called, the following steps are taken:
1. Get the [[Class]] property of this object.
2. Compute a string value by concatenating the three strings "[object ", Result(1), and "]".
3. Return Result(2).
Т.е. метод toString
у любого объекта должен возвращать строку [object [[Class]]]
, где [[Class]]
— внутреннее свойство объекта, недоступное из пользовательских скриптов. У массивов оно равно Array
, у строк — String
, у чисел — Number
, у регулярных выражений — RegExp
. Таким образом функция Object.prototype.toString
, вызванная в контексте массива, должна вернуть строку [object Array]
. Поэтому можно написать такой isArray
.
function isArray(o) {
return Object.prototype.toString.call(o) == '[object Array]';
}
alert(isArray([])); // true
alert(isArray(3)); // false
alert(isArray({})); // false
А можно и ещё короче.
function isArray(o) {
return {}.toString.call(o) == '[object Array]';
}
Такой метод не сможет верно определить массив только если переопределить Object.prototype.toString
, а такая ситуация, к счастью, случается крайне редко.
Для начала нужно понять, а зачем объект приводить к массиву. Как правило, это нужно для того, чтобы стали доступны соответствующие методы, но в большинстве случаев для использования этих методов не обязательно использовать массив. Практически все методы у массивов реализованы так, чтобы работать в тех случаях, если они были вызваны не в контексте массива.
function f() {
return [].slice.call(arguments, 1, 4);
}
alert(f(1, 2, 3, 4, 5)); // 2,3,4
К сожалению в Internet Explorer младше 9-й версии мир DOM — это особый мир, который плохо уживается с миром JavaScript. Поэтому вызов в контексте DOM-объекта метода, взятого у массива, вызовет ошибку.
function f() {
return [].slice.call(document.getElementsByTagName('div'), 0, 2);
}
f(); // Во всех браузерах вернёт массив из первых двух элементов div
// В IE вызовет ошибку
Если вы пишете кроссбраузерный скрипт, то вам придётся избегать таких конструкций. Если же IE не входит в круг поддерживаемых браузеров, то скорее всего приведение к массиву вам и не понадобится.
Однако, в IE нет нативной реализации итераторов и indexOf
/lastIndexOf
, поэтому, если их правильно реализовать, то ими можно кроссбраузерно пользоваться не только для массивов.
function f() {
return [].filter.call(document.getElementsByTagName('div'), function(el) {
return el.offsetWidth;
});
}
f(); // Вернёт массив элементов div, у которых положительная ширина.
Если же всё-таки требуется, преобразовать кроссбраузерно объект в массив возможно несколькими способами. Для объекта arguments
достаточно вызвать метод slice
.
function f() {
return [].slice.call(arguments, 0);
}
alert(typeof f(1, 2, 3)); // true
Если для IE реализованы методы map
или filter
, то ими можно преобразовать любой объект в массив.
// В обоих случаях получим массив элементов
[].map.call(document.getElementsByTagName('div'), function(e) { return e; });
[].filter.call(document.getElementsByTagName('div'), function() { return true; });
Если ни один из способов не подходит, придётся создавать новый пустой массив и вручную добавлять туда элементы, чем впрочем и занимаются методы map
и filter
.
var a = [];
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
a.push(divs[i]);
}