TL; DR
Но есть лотов больше для изучения, читайте дальше ...
JavaScript имеет мощную семантику для циклического перемещения по массивам и объектам, подобным массивам Я разделил ответ на две части: опции для подлинных массивов и опции для вещей, которые просто массив- , как , такие как объект arguments
, другие итерируемые объекты (ES2015 +), коллекции DOM, и так далее.
Я быстро отмечу, что вы можете использовать опции ES2015 сейчас , даже на двигателях ES5, перенося ES2015 на ES5. Ищите «ES2015 транспилирование» / «ES6 транспилирование», чтобы узнать больше ...
Хорошо, давайте посмотрим на наши варианты:
для фактических массивов
У вас есть три опции в ECMAScript 5 ("ES5"), наиболее широко поддерживаемая на данный момент версия, и еще две добавлены в ECMAScript 2015 ("ES2015", "" ES6" ):
- Использование
forEach
и связанных с ним (ES5 +)
- Используйте простую
for
петлю
- Используйте
for-in
правильно
- Использовать
for-of
(неявно использовать итератор) (ES2015 +)
- Использование итератора в явном виде (ES2015 +)
подробности:
1. Используйте forEach
и связанные
В любой смутно современной среде (например, не в IE8), где у вас есть доступ к Array
функциям, добавленным ES5 (напрямую или с использованием полифилов), вы можете использовать forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
принимает функцию обратного вызова и, необязательно, значение для использования в качестве this
при вызове этого обратного вызова (не используется выше). Обратный вызов вызывается для каждой записи в массиве, чтобы пропустить несуществующие записи в разреженных массивах. Несмотря на то, что я использовал только один аргумент выше, обратный вызов вызывается с тремя: значением каждой записи, индексом этой записи и ссылкой на массив, по которому вы перебираете (в случае, если ваша функция еще не имеет этого под рукой) ).
Если вы не поддерживаете устаревшие браузеры, такие как IE8 (на долю которых NetApps приходится чуть более 4% рынка на момент написания этой статьи в сентябре 2016 года), вы можете с радостью использовать forEach
на веб-странице общего назначения без прокладки. Если вам требуется поддержка устаревших браузеров, легко выполнить shimming / polyfilling forEach
(найдите «es5 shim» для нескольких вариантов).
forEach
имеет то преимущество, что вам не нужно объявлять переменные индексации и значения в содержащей области, так как они передаются в качестве аргументов функции итерации и так хорошо подходят только для этой итерации.
Если вас беспокоит стоимость выполнения вызова функции для каждой записи массива, не беспокойтесь; подробности .
Кроме того, forEach
- это функция «проходить через них все», но ES5 определила несколько других полезных функций «прокладывать себе путь через массив и делать вещи», включая:
every
(прекращает цикл при первом возврате обратного вызова false
или что-то фальшивое)
some
(прекращает зацикливание при первом возвращении обратного вызова true
или что-то правдивое)
filter
(создает новый массив, включающий элементы, в которых функция фильтра возвращает true
, и пропускает те, в которых она возвращает false
)
map
(создает новый массив из значений, возвращаемых обратным вызовом) reduce
(создает значение путем многократного вызова обратного вызова, передачи предыдущих значений; подробности см. В спецификации; полезно для суммирования содержимого массива и многих других вещей) reduceRight
(подобно reduce
, но работает в порядке убывания, а не в порядке возрастания)
2.Используйте простой for
цикл
Иногда старые способы являются лучшими:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Если длина массива не изменится во время цикла, и он находится в чувствительном к производительности коде (маловероятно), это немного более сложная версия, захватывающая длину впередможет быть крошечный немного быстрее:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
И / или считая в обратном направлении:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Но при использовании современных движков JavaScript вам редко приходится вытаскивать последний кусок сока.
В ES2015 и выше вы можете сделать свой индекси переменные значения, локальные для цикла for
:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
И когда вы делаете это, не только value
, но и index
воссоздается для каждой итерации цикла, то есть замыкания, созданные в теле цикла, сохраняют ссылку наindex
(и value
), созданный для этой конкретной итерации:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Если у вас было пять делений, вы получите «Индекс: 0», если вы нажали первый, и «Индекс: 4», если вы нажалипрошлой.Это не работает, если вы используете var
вместо let
.
3.Используйте for-in
правильно
Вы получите людей, говорящих вам использовать for-in
, но это не то, что for-in
для .for-in
перебирает перечисляемые свойства объекта , а не индексы массива. Заказ не гарантируется , даже в ES2015 (ES6).ES2015 + определяет порядок свойств объекта (через [[OwnPropertyKeys]]
, [[Enumerate]]
и вещи, которые используют их как Object.getOwnPropertyKeys
), но это не определяет, что for-in
будет следовать этому порядку.(Подробности в этот другой ответ .)
Единственные реальные случаи использования for-in
в массиве:
- Это sparse массивы с массивными пробелами или
- Вы используете неэлементные свойства и хотите включить их в цикл
Рассматривая только первый пример: вы можете использовать for-in
, чтобы посетить эти элементы массива резерва, если вы используете соответствующие меры безопасности:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Обратите внимание на три проверки:
То, что объект имеет свое собственное свойство с таким именем (неодин, который он наследует от своего прототипа), и
что ключ - все десятичные цифры (например, обычная строковая форма, а не научная запись), и
То, что значение ключа при приведении к числу равно <= 2 ^ 32 - 2 (что составляет 4 294 967 294).Откуда этот номер?Это часть определения индекса массива <a href="https://tc39.github.io/ecma262/#array-index" rel="noreferrer"> в спецификации .Другие числа (нецелые числа, отрицательные числа, числа больше 2 ^ 32 - 2) не являются индексами массива.Причина, по которой он равен 2 ^ 32 - 2 , заключается в том, что наибольшее значение индекса становится на единицу меньше, чем 2 ^ 32 - 1 , что является максимальным значением, которое может иметь массив length
.(Например, длина массива соответствует 32-разрядному целому числу без знака.) (Попросил RobG указать в комментарии к моему сообщению в блоге , что мой предыдущий тест был не совсем верным.)
Вы бы не сделали этого во встроенном коде, конечно.Вы бы написали служебную функцию.Возможно:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
4.Используйте for-of
(неявно используйте итератор) (ES2015 +)
ES2015 добавляет итераторы в JavaScript.Самый простой способ использовать итераторы - это новый оператор for-of
.Это выглядит так:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Под прикрытием, он получает итератор из массива и проходит по нему, получая из него значения.В этом нет проблемы, связанной с использованием for-in
, поскольку он использует итератор, определенный объектом (массивом), а массивы определяют, что их итераторы выполняют итерацию через свои записи (не их свойства).В отличие от for-in
в ES5, порядок посещения записей - это порядковый номер их индексов.
5.Использовать итератор явно (ES2015 +)
Иногда вам может понадобиться явно использовать итератор .Вы можете сделать это тоже, хотя это намного более грубое, чем for-of
.Это выглядит так:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Итератор - это объект, соответствующий определению итератора в спецификации.Его next
метод возвращает новый объект результата каждый раз, когда вы вызываете его.Объект результата имеет свойство done
, сообщающее нам, сделано ли оно, и свойство value
со значением для этой итерации.(done
необязательно, если оно будет false
, value
необязательно, если оно будет undefined
.)
Значение value
варьируется в зависимости от итератора;Массивы поддерживают (как минимум) три функции, которые возвращают итераторы:
values()
: это та, которую я использовал выше.Он возвращает итератор, где каждый value
является записью массива для этой итерации ("a"
, "b"
и "c"
в предыдущем примере). keys()
: возвращает итератор, где каждыйvalue
является ключом для этой итерации (поэтому для нашего a
выше это будет "0"
, затем "1"
, затем "2"
). entries()
: Возвращает итератор, гдекаждый value
является массивом в форме [key, value]
для этой итерации.
Для объектов, подобных массиву
Помимо истинных массивов, есть также array-например объекты, которые имеют свойство length
и свойства с числовыми именами: NodeList
экземпляры, arguments
объект и т. д. Как мы можем просмотреть их содержимое?
Использовать любой из параметроввыше для массивов
По крайней мере некоторые, а возможно, большинство или даже все из вышеупомянутых подходов к массивам часто одинаково хорошо применимы к объектам, подобным массивам:
Используйте forEach
и связанные с ним (ES5 +)
Различные функции на Array.prototype
являются "преднамеренно общими"ic "и обычно может использоваться для объектов, подобных массиву, через Function#call
или Function#apply
.(См. Предупреждение о предоставляемых хостом объектах в конце этого ответа, но это редкая проблема.)
Предположим, вы хотите использовать forEach
для свойства Node
* childNodes
.Вы бы сделали это:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Если вы собираетесь делать это много, вы можете скопировать ссылку на функцию в переменную для повторного использования, например:
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;
// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Используйте простой цикл for
Очевидно, что простой цикл for
применяется к объектам, подобным массиву.
Используйте for-in
правильно
for-in
с теми же мерами защиты, что и с массивом, также должно работать с объектами, подобными массиву;может применяться предостережение для предоставленных хостом объектов на # 1 выше.
Использовать for-of
(неявно использовать итератор) (ES2015 +)
for-of
будет использовать итератор, предоставленный объектом (если есть);мы должны увидеть, как это работает с различными объектами, похожими на массивы, особенно с хост-объектами.Например, спецификация для NodeList
из querySelectorAll
была обновлена для поддержки итерации.Спецификации для HTMLCollection
из getElementsByTagName
не было.
Использовать итератор явно (ES2015 +)
См. № 4, мыПосмотрим, как будут работать итераторы.
Создание истинного массива
В других случаях вам может потребоваться преобразовать подобный массиву объект в истинный массив.Сделать это на удивление легко:
Использовать slice
метод массивов
Мы можем использовать slice
метод массивов, который, как и другие методы, упомянутые выше, является «преднамеренно родовым» и поэтому может использоваться с объектами, похожими на массивы, например:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Так, например, если мы хотим преобразоватьNodeList
в настоящий массив, мы могли бы сделать это:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
См. Предупреждение о предоставляемых хостом объектах ниже.В частности, обратите внимание, что это не удастся в IE8 и более ранних версиях, которые не позволяют использовать предоставленные хостом объекты как this
.
Use Синтаксис распространения (...
)
Также возможно использовать ES2015 синтаксис распространения с механизмами JavaScript, поддерживающими эту функцию:
var trueArray = [...iterableObject];
Так, например, если мы хотим преобразовать NodeList
в истинный массив, с синтаксисом распространения это становится довольно кратким:
var divs = [...document.querySelectorAll("div")];
Использование Array.from
(спец.) | (MDN)
Array.from
(ES2015 +, но легко заполняется) создает массив из объекта, похожего на массив, при необходимости сначала пропуская записи через функцию отображения.Итак:
var divs = Array.from(document.querySelectorAll("div"));
Или, если вы хотите получить массив имен тегов элементов данного класса, вы должны использовать функцию отображения:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standard function (since `Array.from` can be shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Предупреждение для предоставленных хостом объектов
Если вы используете Array.prototype
функции с предоставленными хостом массивоподобными объектами (списки DOM и другие вещи, предоставляемые браузером, а неДвижок JavaScript), вы должны быть уверены, что тестировали в своих целевых средах, чтобы убедиться, что предоставленный хостом объект ведет себя правильно. Большинство ведут себя правильно (сейчас), но важно проверить.Причина в том, что большинство методов Array.prototype
, которые вы, вероятно, захотите использовать, полагаются на предоставленный хостом объект, дающий честный ответ на абстрактную операцию [[HasProperty]]
.На момент написания статьи браузеры отлично справлялись с этой задачей, но спецификация 5.1 действительно допускала вероятность того, что предоставленный хостом объект может быть не честным.Это в §8.6.2 , несколько абзацев под большой таблицей в начале этого раздела), где говорится:
Хост-объекты могут реализовывать эти внутренние методы любым способомесли не указано иное;например, одна возможность состоит в том, что [[Get]]
и [[Put]]
для конкретного хост-объекта действительно выбирают и сохраняют значения свойств, но [[HasProperty]]
всегда генерирует false .
(я не смог найти эквивалентного словоблудия в спецификации ES2015, но он все равно будет иметь место.) Опять же, на момент написания этой статьи общие объекты, подобные массиву в современных браузерах [NodeList
экземпляры, например] сделать правильно обработать [[HasProperty]]
, но это важно проверить.)