Каков наилучший способ прокрутки набора элементов в JavaScript?

В прошлом и в большинстве моих текущих проектов я обычно использую цикл for следующим образом:

var elements = document.getElementsByTagName('div');
for (var i=0; i<elements.length; i++) {
    doSomething(elements[i]);
}

Я слышал, что использование цикла "reverse while" выполняется быстрее, но у меня нет реального способа подтвердить это:

var elements = document.getElementsByTagName('div'), 
    length = elements.length;

while(length--) {
    doSomething(elements[length]);
}

Что считается лучшей практикой, когда дело доходит до циклизации элементов в JavaScript или любого массива?

Ответ 1

Здесь хорошая форма цикла, который я часто использую. Вы создаете итеративную переменную из оператора for, и вам не нужно проверять свойство длины, которое может быть особенно дорогостоящим, особенно при итерации по NodeList. Однако вы должны быть осторожны, вы не можете использовать его, если любое из значений в массиве может быть "ложным". На практике я использую его только при переборе массива объектов, который не содержит нулей (например, NodeList). Но я люблю его синтаксический сахар.

var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];

for (var i=0, item; item = list[i]; i++) {
  // Look no need to do list[i] in the body of the loop
  console.log("Looping: index ", i, "item" + item);
}

Обратите внимание, что это также может быть использовано для обратной циклы (если ваш список не имеет ['-1'])

var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];

for (var i = list.length - 1, item; item = list[i]; i--) {
  console.log("Looping: index ", i, "item", item);
}

ES6 Обновление

for...of дает вам имя, но не индекс, доступный с ES6

for (let item of list) {
    console.log("Looping: index ", "Sorry!!!", "item" + item);
}

Ответ 2

Обратите внимание, что в некоторых случаях вам нужно выполнить цикл в обратном порядке (но тогда вы также можете использовать i--).

Например, кто-то хотел использовать новую функцию getElementsByClassName для циклизации элементов данного класса и изменения этого класса. Он обнаружил, что только один из двух элементов был изменен (в FF3).
Это потому, что функция возвращает живой NodeList, который, таким образом, отражает изменения в дереве Dom. Прогулка по списку в обратном порядке избегала этой проблемы.

var menus = document.getElementsByClassName("style2");
for (var i = menus.length - 1; i >= 0; i--)
{
  menus[i].className = "style1";
}

При увеличении индекса, когда мы запрашиваем индекс 1, FF проверяет Dom и пропускает первый элемент со стилем2, который является вторым из исходного Dom, поэтому он возвращает третий начальный элемент!

Ответ 3

Мне нравится делать:

 
var menu = document.getElementsByTagName('div');
for (var i = 0; menu[i]; i++) {
     ...
}

Нет никакого вызова длины массива на каждой итерации.

Ответ 4

На риск крича, я бы получил вспомогательную библиотеку javascript, такую как jquery или прототип, которая инкапсулирует логику в хорошие методы - у обоих есть метод/итератор .each, чтобы сделать это - и они оба стремятся сделать это кросс-браузерно-совместимым

РЕДАКТИРОВАТЬ: Этот ответ был опубликован в 2008 году. Сегодня существуют гораздо лучшие конструкции. Этот конкретный случай будет решен с помощью .forEach.

Ответ 5

Я думаю, что использование первой формы - это, вероятно, путь, так как это, вероятно, самая распространенная структура петлей в известной вселенной, и поскольку я не верю, что обратный цикл экономит ваше время в реальности (все еще делает приращение/уменьшение и сравнение на каждой итерации).

Код, который является узнаваемым и читаемым для других, определенно хорош.

Ответ 6

У меня была очень похожая проблема ранее с document.getElementsByClassName(). Я не знал, что такое нодлист в то время.

var elements = document.getElementsByTagName('div');
for (var i=0; i<elements.length; i++) {
    doSomething(elements[i]);
}

Моя проблема заключалась в том, что я ожидал, что элементы будут массивом, но это не так. Возвращение nodelist Document.getElementsByTagName() повторяется, но вы не можете вызвать методы array.prototype на нем.

Однако вы можете заполнить массив такими элементами:

var myElements = [];
for (var i=0; i<myNodeList.length; i++) {                               
    var element = myNodeList[i];
    myElements.push(element);
};

После этого вы можете свободно звонить на .innerHTML или .style или что-то в элементах вашего массива.

Ответ 7

Я тоже советую использовать простой способ (KISS! -)

- но можно было бы найти некоторую оптимизацию, а именно не проверять длину массива более одного раза:

var elements = document.getElementsByTagName('div');
for (var i=0, im=elements.length; im>i; i++) {
    doSomething(elements[i]);
}

Ответ 8

Также см. мой комментарий к тесту Эндрю Хеджеса...

Я просто попытался запустить тест, чтобы сравнить простую итерацию, оптимизацию, которую я представил, и обратное do/while, где элементы в массиве были протестированы в каждом цикле.

И, увы, не удивительно, что три браузера, которые я тестировал, имели очень разные результаты, хотя оптимизированная простая итерация была самой быстрой во всех! -)

Тест:

Массив с 500 000 элементов строит вне реального теста, для каждой итерации раскрывается значение конкретного элемента массива.

Пробный прогон 10 раз.

IE6:

Результаты:

Простой: 984,922,937,984,891,907,906,891,906,906

Среднее значение: 923,40 мс.

Оптимизировано: 766 766 844 797 750 750 765 765 766 766

Среднее значение: 773.50 мс.

Реверс do/while: 3375,1328,1516,1344,1375,1406,1688,1344,1297,1265

Среднее значение: 1593.80 мс. (Обратите внимание на один особенно неудобный результат)

Opera 9.52:

Результаты:

Простой: 344 343 344 359 343 359 344 359 359 359

Среднее значение: 351,30 мс.

Оптимизировано: 281,297,297,297,297,281,281,297,281,281

В среднем: 289,00 мс

Реверс do/while: 391,407,391,391,500,407,407,406,406,406

Среднее значение: 411.20 мс.

FireFox 3.0.1:

Результаты:

Простой: 278 251 259 245 243 242 259 246 247 256

Среднее значение: 252.60 мс.

Оптимизировано: 267 222 223 226 223 230 301 231 224 230

В среднем: 229,70 мс.

Реверс do/while: 414,381,389,383,388,389,381,387,400,379

Среднее значение: 389,10 мс.

Ответ 9

Форма цикла, предоставляемая Хуан Мендес, очень полезна и практична, Я немного изменил его, так что теперь он работает с - ложными, нулевыми, нулевыми и пустыми строками.

var items = [
    true,
    false,
    null,
    0,
    ""
];

for(var i = 0, item; (item = items[i]) !== undefined; i++)
{
    console.log("Index: " + i + "; Value: " + item);
}

Ответ 10

Я знаю, что вы не хотите это слышать, но: я считаю, что наилучшая практика является наиболее читаемой в этом случае. Пока цикл не будет отсчитываться отсюда до Луны, коэффициент усиления производительности не будет достаточным.

Ответ 11

Вы также можете проверить эту страницу на моем сайте, где я сравниваю скорость приращения for, обратный цикл do/while и устройство Duff.

Ответ 12

Я предпочитаю цикл for, поскольку он более читабельен. Зацикливание от длины до 0 было бы более эффективным, чем цикл от 0 до длины. И использование инвертированного цикла while более эффективно, чем петля, как вы сказали. У меня больше нет ссылки на страницу со сравнительными результатами, но я помню, что разница в разных браузерах различалась. Для некоторых браузеров обратный цикл while был в два раза быстрее. Однако не имеет значения, если вы зацикливаете "маленькие" массивы. В примере вашего примера длина элементов будет "маленькой"

Ответ 13

Я думаю, у вас есть две альтернативы. Для элементов dom, таких как jQuery и подобных фреймворков, вы получаете хороший метод итерации. Второй подход - цикл for.

Ответ 14

Мне нравится использовать TreeWalker, если набор элементов - это дети с корнем node.

Ответ 15

Я знаю, что этот вопрос старый, но здесь другое, чрезвычайно простое решение...

var elements = Array.from(document.querySelectorAll("div"));

Тогда его можно использовать как любой стандартный массив.