TL; DR
Но есть еще много интересного, читайте дальше ...
JavaScript имеет мощную семантику для циклического перемещения по массивам и объектам, похожим на массивы. Я разделил ответ на две части: параметры для подлинных массивов и параметры для объектов, похожих на массивы , таких как arguments
объект, другие итерируемые объекты (ES2015 +), коллекции DOM и т. Д.
Я быстро отметить , что вы можете использовать ES2015 варианты в настоящее время , даже на двигателях ES5, по transpiling ES2015 в ES5. Искать "ES2015 transpiling" / "ES6 transpiling" для получения дополнительной информации ...
Хорошо, давайте посмотрим на наши варианты:
Для фактических массивов
У вас есть три варианта в 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
веб-страницу общего назначения без прокладки. Если вам требуется поддержка устаревших браузеров, forEach
легко выполнить shimming / polyfilling (найдите « 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
будет следовать этому порядку; ES2020 сделал, хотя. (Подробности в этом другом ответе .)
Единственными реальными вариантами использования для for-in
массива являются:
- Это редкие массивы с большими пробелами в них, или
- Вы используете неэлементные свойства и хотите включить их в цикл
Рассмотрим только первый пример: вы можете использовать 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). Откуда этот номер? Это часть определения индекса массива в спецификации . Другие числа (нецелые числа, отрицательные числа, числа больше 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]
для этой итерации.
Для массивоподобных объектов
Помимо истинных массивов, существуют также массоподобные объекты, у которых есть length
свойство и свойства с числовыми именами: NodeList
экземпляры, arguments
объект и т. Д. Как мы перебираем их содержимое?
Используйте любой из параметров выше для массивов
По крайней мере, некоторые, и, возможно, большинство или даже все вышеописанные подходы к массивам часто одинаково хорошо применимы к объектам, подобным массивам:
Использование forEach
и связанные (ES5 +)
Различные функции Array.prototype
являются «преднамеренно общими» и обычно могут использоваться в объектах, подобных массивам, через Function#call
или Function#apply
. (См. Caveat для предоставленных хостом объектов в конце этого ответа, но это редкая проблема.)
Предположим , вы хотите использовать forEach
на множестве А Node
«S 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
from querySelectorAll
была обновлена для поддержки итерации. Спец для HTMLCollection
от getElementsByTagName
не было.
Используйте итератор явно (ES2015 +)
Смотрите № 4.
Создать истинный массив
В других случаях вы можете захотеть преобразовать подобный массиву объект в истинный массив. Делать это на удивление легко:
Используйте slice
метод массивов
Мы можем использовать slice
метод массивов, который, как и другие методы, упомянутые выше, является «преднамеренно родовым» и поэтому может использоваться с объектами, похожими на массивы, например так:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Так, например, если мы хотим преобразовать a NodeList
в истинный массив, мы могли бы сделать это:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Смотрите Caveat для предоставленных хостом объектов ниже. В частности, обратите внимание, что это не удастся в IE8 и более ранних версиях, которые не позволяют вам использовать предоставленные хостом объекты, как this
это.
Использовать распространенный синтаксис ( ...
)
Также возможно использовать синтаксис распространения ES2015 с механизмами JavaScript, которые поддерживают эту функцию. Например for-of
, здесь используется итератор, предоставленный объектом (см. # 4 в предыдущем разделе):
var trueArray = [...iterableObject];
Так, например, если мы хотим преобразовать a NodeList
в истинный массив, с распространенным синтаксисом это становится довольно кратким:
var divs = [...document.querySelectorAll("div")];
использование Array.from
Array.from
(спец) | (MDN) (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]]
правильно, но важно проверить.)
forEach
и не толькоfor
. как уже говорилось, в C # это было немного по-другому, и это меня смутило :)