Каков наилучший способ прокрутки набора элементов в 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"));
Тогда его можно использовать как любой стандартный массив.