Самый быстрый способ конвертировать JavaScript NodeList в массив?

251

Ранее отвеченные вопросы здесь говорили, что это самый быстрый способ:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

При тестировании моего браузера я обнаружил, что он более чем в 3 раза медленнее, чем этот:

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

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

Это странная вещь в моем браузере (Chromium 6)? Или есть более быстрый способ?

РЕДАКТИРОВАТЬ: Для всех, кто заботится, я остановился на следующем (который, кажется, самый быстрый в каждом браузере, который я тестировал):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2: я нашел еще более быстрый способ

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
источник
2
arr[arr.length] = nl[i];может быть быстрее, чем, arr.push(nl[i]);поскольку он избегает вызова функции.
Luc125
9
Эта страница JSPerf является отслеживание всех ответов на этой странице: jsperf.com/nodelist-to-array/27
плов
Обратите внимание, что «EDIT2: я нашел более быстрый путь» на 92% медленнее в IE8.
Камило Мартин
2
Поскольку вы уже знаете, сколько узлов у вас есть:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
мемы
@ Luc125 Это зависит от браузера, так как реализация push может быть оптимизирована, я думаю о chrome, потому что v8 хорош с такими вещами.
axelduch

Ответы:

197

Второй, как правило, работает быстрее в некоторых браузерах, но суть в том, что вы должны использовать его, потому что первый просто не кросс-браузерный. Хотя времена они меняют

@kangax ( IE 9 превью )

Array.prototype.slice теперь может конвертировать определенные хост-объекты (например, NodeList's) в массивы - то, что большинство современных браузеров смогли сделать довольно давно.

Пример:

Array.prototype.slice.call(document.childNodes);
gblazex
источник
??? Оба являются кросс-браузерными - Javascript (по крайней мере, если он заявляет о совместимости со спецификацией ECMAscript) - это Javascript; Массив, прототип, слайс и вызов - все это функции базового языка + типы объектов.
Джейсон С
6
но они не могут быть использованы на NodeLists в IE (я знаю, что это отстой, но эй, смотрите мое обновление)
gblazex
9
поскольку NodeLists не являются частью языка, они являются частью DOM API, который, как известно, содержит ошибки / непредсказуемость, особенно в IE
gblazex
3
Array.prototype.slice не является кросс-браузерным, если принять во внимание IE8.
Лайош Месарос
1
Да, именно об этом мой ответ был в основном :) Хотя это было более актуально в 2010 году, чем сегодня (2015).
gblazex
224

С ES6 у нас теперь есть простой способ создать массив из NodeList: Array.from()функция.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
источник
Как этот новый метод ES6 сравнивается по скорости с другими, упомянутыми выше?
user5508297
10
@ user5508297 Лучше, чем трюк вызова слайса. Медленнее, чем циклы for, но это не совсем то, что мы можем захотеть получить массив без его обхода. А синтаксис красивый, простой и легко запоминающийся!
webdif
Хорошая вещь о Array.from в том, что вы можете использовать аргумент карты:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde
103

Вот новый крутой способ сделать это с помощью оператора распространения ES6 :

let arr = [...nl];
Александр Олссон
источник
7
Не работал для меня в машинописи. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Алиреза Мириан
1
Приближается к 3-му быстрому результату за 3-мя несменными методами с использованием Chrome 71: jsperf.com/nodelist-to-array/90
BrianFreud,
19

Некоторые оптимизации:

  • сохранить длину NodeList в переменной
  • явно установить длину нового массива перед установкой.
  • получить доступ к индексам, а не толкать или сдвигать.

Код ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
тайский
источник
Вы можете сэкономить немного больше времени, используя Array (length), а не создавая массив, а затем отдельно определяя его размер. Если затем вы используете const для объявления массива и его длины с помощью let внутри цикла, это заканчивается примерно на 1,5% быстрее, чем в предыдущем методе: const a = Array (nl.length), c = a.length; for (пусть b = 0; b <c; b ++) {a [b] = nl [b]; } см. jsperf.com/nodelist-to-array/93
BrianFreud,
15

Результаты будут полностью зависеть от браузера, чтобы дать объективный вердикт, мы должны сделать некоторые тесты производительности, вот некоторые результаты, вы можете запустить их здесь :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

IE9 Platform Preview 3:

CMS
источник
1
Интересно, как обратный цикл for выдерживает эти ... for (var i=o.length; i--;)... цикл for в этих тестах переоценивал свойство длины на каждой итерации?
Дагг Наббит
14

Самый быстрый и кросс-браузер

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

Как я сравнил в

http://jsbin.com/oqeda/98/edit

* Спасибо @CMS за идею!

Chromium (аналог Google Chrome) Fire Fox опера

Фелипе Буччони
источник
1
ссылка, кажется, неправильная, должна быть 91 вместо 89, чтобы включить тест, который вы упоминаете. И 98 кажется наиболее полным.
Ярослав Яковлев
9

В ES6 вы можете использовать:

  • Array.from

    let array = Array.from(nodelist)

  • Оператор спреда

    let array = [...nodelist]

Изабелла
источник
Не добавляет ничего нового, что уже было написано в предыдущих ответах.
Стефан Беккер
7
NodeList.prototype.forEach = Array.prototype.forEach;

Теперь вы можете сделать document.querySelectorAll ('div'). ForEach (function () ...)

Джон Уильямс
источник
Хорошая идея, спасибо @Джон! Тем NodeListне менее, не работает, но Objectэто: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ян Кэмпбелл
3
Не используйте Object.prototype: он ломает JQuery и массу вещей, таких как буквальный синтаксис словаря.
Нейт Симер
Конечно, избегайте расширять встроенные встроенные функции.
Роланд
5

быстрее и короче:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
анонимный
источник
3
Почему то >>>0? И почему бы не поместить назначения в первую часть цикла for?
Камило Мартин
5
Кроме того, это глючит. Когда lесть 0, цикл закончится, поэтому 0элемент th не будет скопирован (помните, что в индексе есть элемент 0)
Camilo Martin
1
Люблю этот ответ, но ... Любой, кто интересуется: здесь >>> может не быть необходимости, но он используется, чтобы гарантировать, что длина нодлиста соответствует спецификации массива; это гарантирует, что это 32-разрядное целое число без знака. Проверьте это здесь ecma-international.org/ecma-262/5.1/#sec-15.4 Если вам нравится нечитаемый код, используйте этот метод с предложениями @ CamiloMartin!
Тодд
В ответ на @CamiloMartin - рискованно помещать varвнутрь первой части forцикла из-за «поднятия переменной». Объявление varбудет «поднято» в верхнюю часть функции, даже если varстрока появляется где-то внизу, и это может вызвать побочные эффекты, которые не очевидны из последовательности кода. Например , некоторые код в одной и той же функции происходит прежде , чем для цикла может зависеть от aи lбудучи недекларируемой. Поэтому для большей предсказуемости объявите свои переменные в верхней части функции (или, если на ES6, используйте constили letвместо этого, которые не поднимаются).
brennanyoung
3

Прочтите этот пост здесь, в котором говорится об одном и том же. Из того, что я понял, дополнительное время может быть связано с ходом по цепочке прицелов.

Вивин Палиат
источник
Интересный. Я только что провел несколько похожих тестов, и Firefox 3.6.3 не показывает увеличения скорости, выполняя это в любом случае, в то время как Opera 10.6 имеет увеличение на 20%, а Chrome 6 - на 230% (!), Делая это вручную, повторяя итерацию.
jairajs89
@ jairajs89 довольно странно. Похоже, что это Array.prototype.sliceзависит от браузера. Интересно, какой алгоритм использует каждый из браузеров?
Вивин Палиат
3

Это функция, которую я использую в моем JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}
Веб-дизайнер
источник
1

Вот графики, обновленные на дату публикации («неизвестная платформа» - Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Хром 68.0.3440.75 Internet Explorer 11.15.16299.0

Исходя из этих результатов, кажется, что метод preallocate 1 является самой безопасной кросс-браузерной ставкой.

TomSlick
источник
1

Предполагая nodeList = document.querySelectorAll("div"), что это краткая форма преобразования nodelistв массив.

var nodeArray = [].slice.call(nodeList);

Смотри, я использую это здесь .

Удо Э.
источник