Массив в JavaScript является глобальным объектом, приправленным определёнными методами и свойствами для работы с массивами, прототипом которому служит Object. По своей сути массивы в JS представляют коллекцию данных с числовым целым положительным индексом (неформально могут применяться строковые, отрицательные и дробные «индексы», см. ниже). Таким образом, от стандартных объектов в JS массивы отличает специальное оперирование свойствами с числовыми индексами и наличием свойства length.
Общая информация.
Именно за счёт того, что объекты в JavaScript’е, состоящие из пар ключ-значение являются хэш-таблицами, а плотные (dense) или не разреженные массивы (подробнее об этом речь пойдет ниже) хранятся в виде C-array, мы получаем оптимизацию от JS движков и увеличение скорости обработки данных.
Максимальная длина массива (он же максимальный индекс) равна положительному 32-ух битному целому (4,294,967,295). Свойство массива length возвращает номер последнего индекса + 1, а не количество элементов массива, как многие ожидают.
Для поддержки работы со списком по принципу FIFO можно использовать методы unshift и pop. Отмечу, что методы push/pop выполняются быстрее, чем а shift/unshift. Это связано с тем, что во втором случае приходится полностью перестраивать (индексировать) массив. Рекомендуется использовать эти четыре метода вместо ручного добавления\удаления элементов (a[2] = ‘test’, delete a[2] и пр.) для гарантии отсутствия «дырок» в массиве и установки корректного значения для свойства length.
Перебор элементов массива.
Самое частое использование — это обычный цикл for:
var myArr = [1, 2, 3, 4, 5, 6, 7];
for (var i = 0, len = myArr.length; i < len; i++) {
//
}
или более читаемый подход, правда пересчёт начинается с конца массива
var myArr = [1, 2, 3, 4, 5, 6, 7];
var i = myArr.length;
while (i--) {
//
}
Это самые быстрые переборы, однако, обратите внимание на то, что они не проверяют задано ли элементу значение. Т.е. внутри цикла, скорее всего, вам понадобится также условие:
if (typeof myArr[i] !== 'undefined') {
//
}
Применять для обхода массива цикл for in считается плохой практикой, поскольку данный цикл предназначен для обхода объектов.
var myArr = [1, 2, 3, 4, 5, 6, 7];
for (var i in myArr) {
//
}
Чем же плохи эти подходы? Это трудно-читаемый код и нужно заводить дополнительную переменную (-ые). Также в первом случае для цикла for мы делаем очень много лишних «телодвижений»: присвоение переменной начального значения, проверка на окончание цикла, изменение переменной. А нашей задачей является лишь доступ к элементам массива. Всё это не в духе функционального программирования.
Сравните два подхода.
var myArr = [1, 2, 3];
for (var i = 0, len = myArr.length; i < len; i++) {
alert(myArr[i]);
}
и тоже самое через функцию forEach, появившейся в ECMAScript5:
[1, 2, 3].forEach(alert);
Очевидно, что второй вариант гораздо лаконичнее и понятнее. Поэтому старайтесь использовать методы forEach, some, every и т.д. Хотя, здесь стоит отметить один немаловажный факт, эти методы не поддерживаются IE версией 8 и ниже. Для старых браузеров можно использовать полифилы: forEach, some, every.
Совместимость данных функций и браузеров:
- Opera 11+
- Firefox 3.6+
- Safari 5+
- Chrome 8+
- Internet Explorer 9+
Сортировка.
Для сортировки массивов в JS есть специальный метод sort. По умолчанию сортировка осуществляется по номеру символа в Unicode.
var fruit = ["apples", "bananas", "Cherries"];
fruit.sort(); // ["Cherries", "apples", "bananas"];
var scores = [1, 2, 10, 21];
scores.sort(); // [1, 10, 2, 21]
var things = ["word", "Word", "1 Word", "2 Words"];
things.sort(); // ["1 Word", "2 Words", "Word", "word"]
В Unicode числа идут перед буквами в верхнем регистре, которые, в свою очередь, перед буквами в нижнем регистре.
В качестве аргумента в метод sort можно передать функцию для определения сортировки. Пользовательская функция сортировки может возвращать три значения:
- число больше нуля, если условие сортировки истинно;
- число меньше нуля, если условие сортировки ложно;
- нуль (0), если сравниваемые значения равны.
Пример:
function compare(a, b) {
if (a меньше, чем b по некоторому критерию) {
return -1;
} else if (a больше, чем b по некоторому критерию) {
return 1;
}
// a эквивалентно b
return 0;
}
var numbers = [7, 6, 1, 4, 2, 3, 5];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7]
var items = [
{ name: "Edward", value: 21 },
{ name: "Sharpe", value: 37 },
{ name: "And", value: 45 },
{ name: "The", value: -12 },
{ name: "Magnetic" },
{ name: "Zeros", value: 37 }
];
items.sort(function (a, b) {
if (a.name > b.name) {
return 1;
} else if (a.name < b.name) {
return -1;
}
return 0;
});
/*
{ name: "And", value: 45 },
{ name: "Edward", value: 21 },
{ name: "Magnetic" },
{ name: "Sharpe", value: 37 },
{ name: "The", value: -12 },
{ name: "Zeros", value: 37 }
*/
Псевдо-массив arguments
Данный объект служит для доступа к параметрам, переданным в функцию. Но почему псевдо? Потому что имеет свойство length, числовые ключи, но не имеет никакого отношения к Array, т.е. вы не сможете применить методы массива к arguments.
(function f() {
console.log(arguments instanceof Array); // false
})();
function f(arg1, arg2, ..., argN) {
// arg1 === arguments[0]
// arg2 === arguments[1]
// ...
// argN === arguments[N]
}
arguments часто применяется для доступа к «лишним» аргументам функции, т.е. тем аргументам, которые не были указаны.
function user(name) {
if (arguments.length > 1) {
// arguments[1] === 'Smith'
}
}
user('John', 'Smith');
В JavaScript arguments ссылается на переменные-параметры. Поэтому изменение arguments несёт за собой изменение переменной и наоборот.
function f(x, y) {
console.log(x); // 2
arguments[0] = 1;
console.log(x); // 1
x = 3;
console.log(arguments[0]); // 3
}
f(2, 0);
Более того:
var x = {a: 1, b: 2};
function f(x) {
arguments[0].a = 22;
}
console.log(x.a); // 1
f(x);
console.log(x.a); // 22
Для того, чтобы была возможность применять методы из Array.prototype для arguments создаётся копия объекта arguments, преобразованная в массив:
function f() {
var args = Array.prototype.slice.call(arguments);
//
}
Остановимся на этом моменте подробнее.
Если бы arguments был массивом, мы бы просто сделали:
arguments.slice();
Массивы в JavaScript являются экземплярами «класса» Array и, соответственно, имеют методы из Array.prototype. Как уже писалось выше, arguments — это псевдо-массив, не имеющий ничего общего с Array, поэтому в нём не содержится нужных нам методов для работы с массивом.
console(typeof Array.prototype.slice); // function
Как видите slice — это функция. Любая функция в JS имеет методы call и apply для вызова другого метода в заданном контексте.
var args = Array.prototype.slice.call(arguments);
Описание всех методов объекта Array в JavaScript.
Мутаторы (методы, изменяющие массив).
- fill — заполнение массива ОТ и ДО одинаковым значением.
- push — добавление одного или более элементов в конец массива. Возвращает длину нового массива.
- pop — удаляет последний элемент из массива. Возвращает удалённый элемент.
- reverse — изменяет порядок элементов в массиве на противоположный.
- shift — удаляет первый элемент из массива и возвращает его.
- unshift — добавляет один или более элементов в начало массива. Возвращает длину нового массива.
- sort — сортирует элементы массива.
- splice — добавление\удаление элементов в массиве.
Акцессоры (данные методы не вносят изменений в массив и служат для доступа к его элементам).
- concat — возвращает новый массив, полученный путём совмещения других массивов и\или значений.
- join — соединяет все элементы массива в строку.
- slice — извлекает кусок массива и возвращает новый.
- indexOf — возвращает первый индекс элемента в массиве, эквивалентный искомому значению. В случае, если элемент не был найден — возвращает -1.
- lastIndexOf - возвращает последний индекс элемента в массиве, эквивалентный искомому значению. В случае, если элемент не был найден — возвращает -1.
Итераторы (проход по элементам массива, циклы)
- forEach — вызывает функцию для каждого метода массива.
- entries — возвращает новый объект Array Iterator, который содержит пару ключ\значение для каждого индекса массива.
- every — возвращает true, если каждый элемент массива соответствует условию, указанному в передаваемой функции.
- some - возвращает true, если, по-крайней, мере один элемент массива соответствует условию, указанному в передаваемой функции.
- filter - создаёт новый массив со всеми элементами текущего массива для которых указанная фильтрующая функция возвращает true.
- find - возвращает найденное значение массива, если этот элемент соответствует условию в передаваемой функции или undefined в случае неудачного поиска.
- findIndex - возвращает индекс массива, если элемент соответствует условию в передаваемой функции или -1 в случае неудачного поиска.
- keys - возвращает новый Array Iterator, который содержит ключ для каждого индекса в массиве.
- map - создаёт новый массив с результатом вызова передаваемой функции на каждом элементе массива.
- reduce - вызывает заданную функцию для всех элементов в массиве, возвращаемое значение которой представляет собой накопленный результат и предоставляется как аргумент в следующем вызове этой функции. Проход по массиву осуществляется слева-направо.
- reduceRight - то же самое, что и reduce, однако проход начинается справа-налево.
А также методы, унаследованные от Object.prototype: toSource, toString, hasOwnProperty, isPrototypeOf, valueOf и пр.
Трюки, хаки и интересные решения
1) Создание массива через конструктор Array.
var arr1 = new Array(); // Пустой массив, с length равным 0
var arr2 = new Array(5); // Пустой массив, с length равным 5
var arr3 = new Array(1, 2, 3); // Массив с тремя элементами 1, 2 и 3 и length = 3
К слову сказать, что вызов Array как функции мы получим такой же эффект как и с оператором new.
var arr1 = Array(); // Пустой массив, с length равным 0
var arr2 = Array(5); // Пустой массив, с length равным 5
var arr3 = Array(1, 2, 3); // Массив с тремя элементами 1, 2 и 3 и length = 3
2) Переопределение Array.
Пожалуй, это основная причина по которой не стоит использовать конструктор Array для создания массивов в JS.
var Array = 100;
var foo = new Array(); // TypeError: number is not a function
Поэтому используйте для создания массива сокращённую литеральную нотацию:
var myArr = [];
3) Длина массива равна порядковому номеру последнего элемента в нём.
var myArr = [1, 2, 3];
console.log(myArr.length); // 3
myArr[100] = 100;
console.log(myArr.length); // 101
console.log(myArr); // [1, 2, 3, undefined x 97, 100];
console.log(myArr[33]); // undefined
Важное замечание.
Работать с массивами в JS следует как с непрерывным набором данных — плотный (dense) массив, без разрывов в индексах — разреженный (sparse) массив. Например, в случае, если у нас есть некий массив myArr, соответствующий [1, 2, 3], и вы делаете myArr[100] = 100, движок JS начинает работать с массивом, как с обычным объектом, что может привести к потере тех преимуществ, которые даёт нам именно массив.
Также разреженный массив мы получаем, если удаляем элемент массива через delete:
var myArr = [1, 2, 3];
delete myArr[1];Да, это удалит значение «2″ в массиве, однако смещения «3″ на позицию «2″ не произойдет и мы получаем «дырку» между единицей и тройкой.
console.log(myArr); // [1, undefined, 3]Сравним разряженный и плотный массив:
var sparse = [ , , 'c'];
var dense = [undefined, undefined, 'c'];
0 in sparse // false
0 in dense // true
4) Отрицательные, дробные и текстовые индексы в массиве.
var myArr = [1, 2, 3];
console.log(myArr); // [1, 2, 3]
console.lor(myArr.length); // 3
myArr[-1] = 'test1';
myArr['test'] = 'test2';
myArr[1.7] = 'test3';
console.log(myArr); // [1, 2, 3]
console.lor(myArr.length); // 3
console.log(myArr[-1]); // test1
console.log(myArr['test']); // test2
console.log(myArr[1.7]); // test3
dir(myArr);

это достигается за счёт того, что почти всё в JS является объектом. Поэтому мы может делать так:
var myArr = [];
myArr.val = 124;
myArr['val2'] = 'value';
5) Параметр length можно переопределить.
Это может пригодится для удаления элементов массива (усечения).
var myArr = [1, 2, 3, 4, 5, 6, 7];
myArr.length = 3;
console.log(myArr); // [1, 2, 3]
Увеличивая длину массива через length мы не получаем новых элементов. Т.е. по сути элементов, кроме 1, 2 и 3 в данном массиве не существует.
var myArr = [1, 2, 3];
myArr.length = 10;
console.log(myArr); // [1, 2, 3]
console.log(myArr[5]); // undefined
6) Поиск объекта в массиве.
var myArr = [{a: 1}, {b: 1}];
console.log(myArr.indexOf({b: 1})); // -1
Однако, можно поступить следующим образом:
var obj1 = {a: 1};
var obj2 = {a: 2};
var myArr = [];
myArr.push(obj1, obj2);
console.log(myArr.indexOf(obj2)); // 1
7) Проверка наличия индекса в массиве.
var myArr = [1, 2, 3, 4, 5, 6, 'test', 11];
console.log(1 in myArr); // true
console.log('1' in myArr); // true
console.log(3 in myArr); // true
console.log(7 in myArr); // true
console.log('test' in myArr); // false
console.log('test2' in myArr); // false
8) Многомерный массив.
Как такового понятия многомерного массива в JS нет, однако это можно эмулировать:
var a = [
['0x0', '0x1', '0x2']
['1x0', '1x1', '1x2']
['2x0', '2x1', '2x2']
];
console.log(a[2][1]); // 2x1
console.log(a[2][2]); // 2x2
Более правильно это называть массив массивов, нежели, чем многомерный массив.
9) Поиск в массиве с определённой позиции.
var myArr = [1,2,3,4,3,5,3];
console.log(myArr.indexOf(3)); // 2
Но что, если нам необходимо получить тройку по-середине?
console.log(myArr.indexOf(3, 3)); // 4
10) Reduce и undefined.
[1, 2, 3, , , , , , 'hello'].reduce(function(len, el) {
return ++len;
}, 0);
// 4
Как видите, undefined элементы не учитываются.
11) Минимальный и максимальный элемент в массиве.
var foo = [1, 2, 3];
Math.min(foo); // NaN
Math.min.apply(null, foo); // 1
Math.max.apply(null, foo); // 3
12) Слияние массивов.
var foo = [1, 2, 3];
foo.push.apply(foo, [4, 5, 6]);
// или
var foo = [4, 5, 6]
bar = [1, 2, 3].concat(foo); // 1, 2, 3, 4, 5, 6
13) Клонирование массива.
var arrayA = [1, 2, 3];
var arrayB = arrayA;
arrayB[1] = 222;
console.log(arrayB); // [1, 222, 3]
console.log(arrayA); // [1, 222, 3]
Как видите, массивы передаются по ссылке и следовательно, при внесении изменений в один массив мы получим изменения и в другом. Нижеприведённый код позволяет клонировать массив путём создания нового.
var foo = [1, 2, 3];
var bar = foo.slice(0); // bar = [1, 2, 3]
14) Равенство массивов.
Учитывая, что
var a = [1, 2, 3];
var b = [1, 2, 3];
console.log(a === b); // false
и даже
console.log(a == b); // false
нам нужна функция, которая будет проверять эквивалентны ли два массива.
Array.prototype.compare = function(arr) {
if (this.length !== arr.length) {
return false;
}
return arr.join('') === this.join('');
}
var a = [1, 2, 3];
var b = [1, 2, 3];
console.log(a.compare(b)); // true
15) Заполнение массива.
Просто трюк, вряд ли где-либо будет применимо.
var myArr = [null, null, null, null, null];
myArr = myArr.map(function (x, i) {
return i;
});
console.log(myArr); // [0, 1, 2, 3, 4]
var myArr = new Array(5).map(function (x, i) {
return i;
});
console.log(myArr); // []
var myArr = Array.apply(null, Array(5));
myArr = myArr.map(function (x, i) {
return i;
});
console.log(myArr); // [0, 1, 2, 3, 4]
16) Выход за пределы.
var myArr = ['a', 'b', 'c'];
myArr[Math.pow(2,32) - 1] = 'd';
console.log(myArr); // [ 'a', 'b' ]
console.log(arr.length); // 2
console.log(arr[Math.pow(2,32)-1]); // "d"
Как видите, при попытке добавить индекс больше допустимого в массиве элемент не отображается. Однако к нему можно получить доступ по имени, которым у нас в данном случае будет число.
[].length = -1; // RangeError: Invalid array length
[].length = Math.pow(2, 32); // RangeError: Invalid array length
[].length = 'abc'; // RangeError: Invalid array length
А вот со свойством length всё серьёзней. При попытке установить не подходящие значения мы получаем ошибки.
17) Преобразование типов.
!![]; // true
!![1]; // true
[] == false // true
[1,2,3] == [1,2,3]; // false
[] == []; // false
[1,2,3].toString(); // "1,2,3,4"
parseInt([], 10); // NaN
parseInt([12, 22, 23], 10); // 12
typeof []; // "object"
[1, 2, 3] instance of array; // true
[1, 2, 3] instance of object; // true
Дополнительная и расширенная документация и информация:
- http://learn.javascript.ru/array
- http://learn.javascript.ru/array-methods
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
- http://dev.opera.com/articles/javascript-array-extras-in-detail/
- http://es5.github.io/#x15.4
- http://www.2ality.com/2012/06/dense-arrays.html
очень интересная и полезная статья мне понравилось кое какие полезные моменты я узнал для себя спасибо за статью
Привет! Сама я занимаюсь копирайтингом, но стараюсь развиваться в разных областях. Спасибо за хорошую статью. Она для меня оказалась очень полезной.
Привет! Безмерно рад, что не зря потратил время и это кому-то пригодилось.