Найшвидший спосіб перетворити 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)? Або є більш швидкий шлях?

EDIT: Для всіх, хто переймається, я зупинився на наступному (який, здається, є найшвидшим у кожному перевіреному браузері):

//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]));

2
arr[arr.length] = nl[i];може бути швидше, ніж arr.push(nl[i]);оскільки це дозволяє уникнути виклику функції.
Luc125

9
Ця сторінка JSPerf є відстеження всіх відповідей на цій сторінці: jsperf.com/nodelist-to-array/27
плов

Зауважте, що "EDIT2: я знайшов швидший шлях" на IE8 на 92% повільніше.
Каміло Мартін

2
Так як ви знаєте , вже знаєте , скільки вузлів у вас є:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
МЕМС

@ Luc125 Це залежить від браузера, оскільки реалізація натискань може бути оптимізована, я думаю про хром, тому що v8 хороший з подібними матеріалами.
axelduch

Відповіді:


197

Другий має тенденцію бути швидшим у деяких браузерах, але головне в тому, що вам доведеться його використовувати, оскільки перший просто не перехресний браузер. Незважаючи на те, що The Times They Are a Changin '

@kangax ( попередній перегляд IE 9 )

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

Приклад:

Array.prototype.slice.call(document.childNodes);

??? Обидва є сумісними між браузерами - Javascript (принаймні, якщо він стверджує, що він сумісний зі специфікацією ECMAscript) - це Javascript; Масив, прототип, фрагмент та виклик - це всі функції основної мови + типи об'єктів.
Jason S

6
але їх не можна використовувати в NodeLists в IE (я знаю, це відстійно, але ей дивіться моє оновлення)
gblazex

9
оскільки NodeLists не є частиною мови, вони є частиною API DOM, який, як відомо, баггі / непередбачуваний, особливо в 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)

як цей новий метод ES6 по швидкості порівнюється з іншими, про які було сказано вище?
user5508297

10
@ user5508297 Краще, ніж фокус виклику фрагментів. Повільніше, ніж циклі для циклів, але це не зовсім те, що ми можемо захотіти мати масив, не обходячи його. А синтаксис прекрасний, простий і легко запам’ятовується!
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];
}

Ви можете голити трохи більше часу, використовуючи масив (довжина), а не створюючи масив, а потім окремо розміруючи його. Якщо ви використовуєте const для оголошення масиву та його довжини, з пуском всередині циклу, він закінчується приблизно на 1,5% швидше, ніж описаний вище метод: const a = масив (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:

Сафарі 5:

Попередній перегляд платформи IE9 3:


1
Цікаво, як реверс для циклу стоїть проти цих ... for (var i=o.length; i--;)... чи "для циклу" в цих тестах переоцінювали властивість довжини на кожній ітерації?
Dagg Nabbit


9

У ES6 ви можете використовувати:

  • Array.from

    let array = Array.from(nodelist)

  • Оператор розповсюдження

    let array = [...nodelist]


Не додає нічого нового, що вже було написано в попередніх відповідях.
Стефан Бекер

7
NodeList.prototype.forEach = Array.prototype.forEach;

Тепер ви можете зробити document.querySelectorAll ('div'). ForEach (function () ...)


Гарна ідея, дякую @John! Однак, NodeListне працює, але Objectце: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell

3
Не використовуйте Object.prototype: він розбиває JQuery і безліч речей, як синтаксис буквального словника словника.
Нейт Симер

Звичайно, уникайте розширення вбудованих функцій.
roland

5

швидше і коротше:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
Чому >>>0? А чому б не поставити завдання на першій частині циклу for?
Каміло Мартін

5
Також це баггі. Коли lце буде 0, цикл закінчиться, тому 0й елемент не буде скопійовано (пам’ятайте, що є елемент в індексі 0)
Camilo Martin

1
Люблю цю відповідь, але ... Кожен, хто цікавиться: >>> можливо, тут не знадобиться, але він використовується для гарантування довжини ноделіста дотримується специфіки масиву; це гарантує, що це непідписане 32-бітове ціле число. Перевірте це ecma-international.org/ecma-262/5.1/#sec-15.4 Якщо вам подобається нечитабельний код, використовуйте цей метод із пропозиціями @ CamiloMartin!
Тодд

У відповідь на @CamiloMartin - Ставити varвсередину першої частини forциклу ризиковано через "змінну підйомність". Оголошення varбуде "піднято" до верху функції, навіть якщо varрядок з'являється десь нижче вниз, і це може викликати побічні ефекти, які не очевидні з кодової послідовності. Наприклад, деякий код у тій же функції, що виникає перед циклом for, може залежати від aта не lбуде оголошений. Тому для більшої передбачуваності оголосіть ваші параметри у верхній частині функції (або якщо на ES6, використовуйте constабо letзамість цього, які не піднімаються).
brennanyoung

3

Перевірте це повідомлення у блозі тут , що говорить про те ж. З того, що я збираю, додатковий час може мати відношення до підйому ланцюга.


Цікаво. Я тільки що зробив кілька подібних тестів зараз, і Firefox 3.6.3 не показує збільшення швидкості, роблячи це так чи інакше, тоді як Opera 10.6 збільшує 20%, а Chrome 6 збільшує на 230% (!), Роблячи це вручну.
jairajs89

@ jairajs89 досить дивно. Виявляється, що Array.prototype.sliceбраузер залежить. Цікаво, який алгоритм використовує кожен із браузерів.
Vivin Paliath

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):

Сафарі 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

З цих результатів виходить, що метод попереднього розміщення 1 є найбезпечнішою ставкою для перехресних веб-переглядачів.


1

Припускаючи nodeList = document.querySelectorAll("div"), що це стисла форма перетворення nodelistв масив.

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

Побачте, я тут його використовую .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.