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
легко зробити (пошук "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
від querySelectorAll
оновлено для підтримки ітерації. Спеціалізації для з HTMLCollection
боку getElementsByTagName
не було.
Використовуйте ітератор явно (ES2015 +)
Див. №4.
Створіть справжній масив
В іншому випадку ви можете перетворити об’єкт, схожий на масив, у справжній масив. Робити це напрочуд просто:
Використовуйте slice
метод масивів
Ми можемо використовувати slice
метод масивів, який, як і інші методи, згадані вище, є "навмисно загальним", і тому може використовуватися з об'єктами, схожими на масив, наприклад:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Так, наприклад, якщо ми хочемо перетворити 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]]
завжди генерують помилкові .
(Я не міг знайти еквівалентну багатослівність у специфікації ES2015, але це все одно має бути так.) Знову ж таки, з цього написання звичайні об’єкти, подібні до масиву, подібні до масивів у сучасних браузерах [ NodeList
наприклад, наприклад] , обробляють [[HasProperty]]
правильно, але важливо перевірити.)
forEach
і не простоfor
. як було сказано, в c # було трохи інакше, і це мене бентежило :)