Не только массивы


Немассивы в JavaScript Как это ни странно, не всякий объект, имеющий числовые свойства и свойство 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]); }