Итераторы


Итераторы Во всех современных браузерах, кроме 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); });