Во всех современных браузерах, кроме Internet Explorer, у массивов есть методы, предназначенные для перебора элементов и выполнения последующих различных действий над ними. Это методы forEach
, map
, filter
, every
, some
, reduce
и reduceRight
. В IE эти методы отсутствуют, но их можно реализовать, расширив прототип Array
(сжатая версия файла).
Эти методы перебирают элементы массива от 0
до length - 1
и, если элемент существует, передают его в callback-функцию. Наличие элемента проверяется оператором in
, без hasOwnProperty
. Это значит, что перебирутся и элементы, находящиеся в прототипе Array
, если вдруг такое произойдёт. Но на length
свойства прототипа не влияют, следовательно, для того, чтобы это произошло, элемент в прототипе должен быть меньше length
, а в самом массиве на его месте должен быть пропуск.
Array.prototype[3] = 3;
var a = [0];
a[5] = 5;
var elements = [];
a.forEach(function(e) {
elements.push(e);
});
alert(elements); // 0,3,5
Вторым аргументом во все функции, кроме reduce
и reduceRight
передаётся контекст вызова callback-функции. Также стоит отметить, что свойство length
кэшируется до входа в цикл, поэтому, если внутри callback-функции будут добавляться элементы в массив, то перебор всё равно не зациклится.
var a = [0, 1, 2], i = 0;
a.forEach(function(num) {
i++;
a.push(num);
});
alert([i, a.length]); // 3,6
В callback-функцию все методы, кроме, опять же, reduce
и reduceRight
передают элемент массива, его индекс и сам массив. Ни один из этих методов не изменяет исходный массив. Таким образом, реализация forEach
, например, должна иметь вид.
Array.prototype.forEach = function(fn, thisObj) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this) {
fn.call(thisObj, this[i], i, this);
}
}
};
Перебор элементов для выполнения над ними произвольных действий осуществляется методом forEach
. Он просто вызывает callback-функцию для каждого элемента, не производя больше никаких действий.
var a; // Допустим, есть массив объектов, у которых необходимо вызвать метод foo
a.forEach(function(object, index) {
object.foo(index);
});
Callback-функция может быть определена и заранее.
function bar(obj, i) {
obj.foo(i);
}
a.forEach(bar);
Метод map
используется для получения массива, аналогичного исходному, но элементы которого пропущены через callback-функцию. Индексы массива при этом сохраняются, т.е. если в исходном массиве были пропуски, то и в результирующем массиве они тоже будут.
var a = [0, 1, , , , 4, , 8];
var b = a.map(function(x) {
return x + 1;
});
alert(a + '\n' + b); // 0,1,,,,4,,8
// 1,2,,,,5,,9
Метод filter
возвращает массив элементов из исходного массива, для которых callback-функция вернула истинное значение. Индексы этот метод не сохраняет, результирующий массив будет заведомо без пропусков.
// Традиционный вывод чётных/нечётных чисел
alert([0, 1, 2, 3, 4, 5, 6, 7, 8, 9].filter(function(x) { return x % 2; })); // 1,3,5,7,9
Метод every
возвращает true
, если для каждого элемента массива callback-функция возвращает истинное значение. Как только callback вернёт ложное значение, перебор элементов прекращается.
Метод some
возвращает true
, если хотя бы для одного элемента массива callback-функция возвращает истинное значение. Как только callback вернёт истинное значение, перебор элементов заканчивается.
var a = [1, 3, 5, 6, 7, 9];
// Все ли элементы в массиве нечётные?
alert(a.every(function(x) { return x % 2; })); // false
// Есть в массиве хотя бы один чётный элемент?
alert(a.some(function(x) { return !(x % 2); })); // true
Неочевидный вариант использования методов every
и some
— перебор элементов до нужного с прекращением дальнейшего перебора.
var a = [1, 3, 5, 6, 7, 9], firstEven, i = 0;
a.some(function(x) {
i++;
if (!(x % 2)) {
// Нашли нужный элемент, дальнейший перебор нам не нужен
firstEven = x;
return true;
}
});
alert([firstEven, i]); // 6,4
Методы reduce
и reduceRight
последовательно вызывают callback-функцию, передавая ей результат её выполнения на предыдущей итерации и очередной элемент массива. Возвращают они результат последнего вызова callback-функции. reduceRight
отличается от reduce
тем, что элементы перебираются в обратном порядке.
Вторым аргументом обе функции принимают инициирующее значение, которое будет передано при первом вызове callback-функции. Если инициирующее значение не передано, то для первого элемента массива (не обязательно элемента с индексом 0
, а первого имеющегося) callback-функция вызвана не будет, а этот элемент будет инициирующим значением. Если reduce
или reduceRight
будут вызваны без инициирующего значения для пустого массива, то будет брошен TypeError
. Если в массиве один элемент, и инициирующее значение не передано, callback-функция не будет вызвана ни разу, а метод вернёт единственный элемент.
var a = [2, 3, 5, 6, 7, 9];
// Сумма элементов, инициирующее значение можно не передавать
alert(a.reduce(function(sum, x) { return sum + x; })); // 32
// Сумма квадратов, инициирующее значение не передавать нельзя, т.к. иначе
// квадрат первого элемента не посчитается
alert(a.reduce(function(sum, x) { return sum + x * x; }, 0)); // 204
for
Истинные ценители могут совсем отказаться от использования цикла for
и писать все циклы в функциональном стиле. Для этого необходимо написать функцию, создающую массив последовательных чисел. Например
Array.range = function(start, count) {
if (arguments.length == 1) {
count = start;
start = 0;
}
var a = [];
for (var i = start; i < start + count; i++) {
a.push(i);
}
return a;
}
Преимуществом такого подхода является то, что не требуется дополнительно создавать замыкание для сохранения локальных переменных, а переменные из разных циклов не пересекаются друг с другом.
// Добавим в документ пять элементов, при клике на которых выводится его номер
Array.range(5).forEach(function(i) {
var div = document.createElement('div');
div.innerHTML = i;
div.onclick = function() {
alert(i);
};
document.body.appendChild(div);
});