Як перетворити список вузлів DOM на масив у Javascript?


96

У мене є функція Javascript, яка приймає список вузлів HTML, але вона очікує масив Javascript (на ній працюють деякі методи Array), і я хочу подати його на вихід, Document.getElementsByTagNameщо повертає список вузлів DOM.

Спочатку я думав використати щось таке, як:

Array.prototype.slice.call(list,0)

І це прекрасно працює у всіх браузерах, крім звичайно Internet Explorer, який повертає помилку "Очікуваний об'єкт JScript", оскільки, мабуть, список вузлів DOM, повернутий Document.getElement*методами, є недостатньо об'єктом JScript, щоб бути ціллю виклику функції.

Застереження: Я не проти писати певний код Internet Explorer, але мені заборонено використовувати будь-які бібліотеки Javascript, такі як JQuery, оскільки я пишу віджет, який буде вбудований у веб-сайт сторонніх розробників, і я не можу завантажити зовнішні бібліотеки, які створить конфлікт для клієнтів.

Моє останнє зусилля - переглядати список вузлів DOM і створювати сам масив, але чи є кращий спосіб це зробити?


А ще краще - створити функцію для перетворення зі списку вузлів DOM, але це справді моє рішення, я думаю, ви правильно зрозуміли.
Kristoffer Sall-Storgaard

> for (i = 0; i & lt; x.length; i ++) Навіщо отримувати довжину NodeList на кожній ітерації? Це не просто втрата часу, але оскільки NodeLists - це реальні колекції, якщо щось у тілі циклу змінює свою довжину, ви можете циклічно нескінченно цитувати або отримувати індекс поза межами. Останнє є найгіршим, що може статися, якщо ви призначите довжину змінній, а помилка набагато краща, ніж нескінченний цикл.

Це справді давнє запитання, але jQuery був побудований спеціально за допомогою методу .noConflict, щоб він не спричиняв конфлікту з іншими бібліотеками (навіть самою собою), що означає, що на сторінку можна завантажувати кілька версій jQuery. Тим не менш, краще уникати використання / завантаження бібліотеки, якщо це вам не потрібно.
vol7ron

@ vol7ron: перемотування вперед до 2016 року, і всі все ще впевнені щодо розміру, який бібліотеки javascript додають на сторінку. Звичайно, JQuery мініфікований і зібраний на ZIP-файлах становить 30 КБ, його все ще 30 КБ занадто багато, щоб просто перетворити список вузлів :-)
Guss,

Відповіді:


64

NodeLists - це хост-об'єкти , використання Array.prototype.sliceметоду на об'єктах-хостах не гарантується, стверджує специфікація ECMAScript:

Чи може функція зрізу бути успішно застосована до хост-об’єкта, залежить від реалізації.

Я б порекомендував вам зробити просту функцію для ітерації NodeListта додавання кожного існуючого елемента в масив:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

ОНОВЛЕННЯ:

Як підказують інші відповіді, тепер ви можете використовувати в сучасних середовищах синтаксис поширення або Array.fromметод:

const array = [ ...nodeList ] // or Array.from(nodeList)

Але, замислюючись над цим, я вважаю, що найпоширенішим варіантом використання для перетворення NodeList в масив є ітерація над ним, і тепер NodeList.prototypeоб’єкт має forEachметод власним способом , тому, якщо ви перебуваєте в сучасному середовищі, ви можете використовувати його безпосередньо або забруднювач.


2
Це створює масив із зворотним початковим порядком списку, що, на мою думку, не є тим, що хоче OP. Ви мали намір робити array[i] = obj[i]замість цього array.push(obj[i])?
Тім Даун

@Tim, так, я мав це раніше, але редагував учора ввечері, не помічаючи цього (3 ранку за місцевим часом :), Дякую !.
Крістіан С. Сальвадо,

9
За яких обставин може obj.lengthбути щось інше, ніж ціле число?
Пітер

1
Я не можу повірити, що це так складно. Некрасиво. Це дуже часта потреба у програмуванні Web / JS. Новий метод для наступного випуску мови?
Ендрю Копер

1
@AlbertoPerez, ласкаво просимо !. Saludos hasta Madrid!
Крістіан С. Сальвадо

126

У es6 ви можете просто використовувати наступне:

  • Спред-оператор

     var elements = [... nodelist]
  • Використовуючи Array.from

     var elements = Array.from(nodelist)

докладніше на https://developer.mozilla.org/en-US/docs/Web/API/NodeList


4
так просто з Array.from(): D
Хосан Ірачета

4
у випадку, якщо хтось використовує цей підхід з Typescript (до ES5), Array.fromпрацює лише , оскільки TS транспілює це nodelist.slice- що не підтримується.
Пітер Альберт,

Я відповів тим самим за рік до вас, і ви пройшли мене на голосуванні? Я не можу пояснити це ..
vsync

3
@vsync, у вашій відповіді не згадуєтьсяArray.from
ESR

@EdmundReed - так? як це виправдовує це. це довше писати, тому в реальній ситуації це ніколи не буде використано, лише spreadвикористовується.
vsync

16

Використовуючи розповсюдження (ES2015) , це так само просто, як:[...document.querySelectorAll('p')]

(необов’язково: використовуйте Babel для транспіляції вищезазначеного коду ES6 до синтаксису ES5)


Спробуйте це на консолі браузера і побачите магію:

for( links of [...document.links] )
  console.log(links);

Принаймні принаймні chrome, 44, я отримую таке: Uncaught TypeError: document.querySelectorAll не є функцією (…)
Нік

@OmidHezaveh - Як я вже говорив, це код ES6. Я не знаю, чи підтримує Chrome 44 ES6, і якщо так, то з яким покриттям. Це майже річний браузер, і, очевидно, вам доведеться запускати цей код у браузері, який підтримує ES6-розповсюдження.
vsync

Або перекажіть його на es5 перед виконанням
HelloWorld,

8

Скористайтеся цим простим трюком

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})

Чи можете ви пояснити, чому, на вашу думку, у цього більше шансів на успіх, ніж у використанні Array.prototype.slice(або, [].sliceяк ви сказали)? Як примітка, я хотів би зауважити, що специфічна помилка IE, яку я задокументував у Q, трапляється в IE 8 або нижче, де mapв будь-якому випадку не реалізована. В IE 9 ( «Режим стандартів») або вище, як sliceі mapдомогтися успіху таким же чином.
Guss

6

Незважаючи на те, що це насправді не правильна обробка, оскільки немає специфікації, яка вимагає роботи з елементами DOM, я зробив таку, щоб дозволити вам використовувати slice()таким чином: https://gist.github.com/brettz9/6093105

ОНОВЛЕННЯ : Коли я піднімав це з редактором специфікації DOM4 (запитуючи, чи не можуть вони додати власні обмеження для хост-об'єктів (щоб специфікація вимагала від виконавців належного перетворення цих об'єктів при використанні методів масивів) за межами специфікації ECMAScript, яка мала дозволено до незалежності від реалізації), він відповів, що "об'єкти хосту більш-менш застаріли відповідно до ES6 / IDL". Я бачу, за http://www.w3.org/TR/WebIDL/#es-array, що специфікації можуть використовувати цей IDL для визначення "об'єктів масиву платформи", але http://www.w3.org/TR/domcore/ не схоже, що не використовує новий IDL для HTMLCollection(хоча схоже, що він може це робити, Element.attributesхоча він явно стверджує, що використовує WebIDL для DOMString і DOMTimeStamp). Я справді бачу[ArrayClass](який успадковується від Array.prototype) використовується для NodeListNamedNodeMapзараз застарілий на користь єдиного елемента, який все ще використовував би його, Element.attributes). У будь-якому випадку, схоже, це має стати стандартом. ES6 Array.fromтакож може бути зручнішим для таких перетворень, ніж необхідність вказувати Array.prototype.sliceі більш семантично чітке ніж [].slice()(а коротша форма Array.slice()("загальний масив"), наскільки мені відомо, не стала стандартною поведінкою).


Я оновив інформацію про те, що технічні характеристики можуть рухатися у напрямку, що вимагає такої поведінки.
Brett Zamir

5

Сьогодні, у 2018 році, ми могли б використовувати ECMAScript 2015 (6-е видання) або ES6, але не всі браузери можуть це зрозуміти (наприклад, IE не розуміє всього цього). Якщо ви хочете, ви можете використовувати ES6 наступним чином: var array = [... NodeList];( як оператор поширення ) або var array = Array.from(NodeList);.

В іншому випадку (якщо ви не можете використовувати ES6), ви можете скористатися найкоротшим способом перетворення а NodeListв Array:

var array = [].slice.call(NodeList, 0);.

Наприклад:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Але якщо ви хочете DOMлегко переглядати список вузлів, то вам не потрібно перетворювати a NodeListна Array. Можна циклічно перебирати елементи за NodeListдопомогою:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Не спокушайтеся використовувати for...inабо for each...inперераховувати елементи у списку, оскільки це також буде перераховувати довжину та властивості елементів NodeListі викликати помилки, якщо ваш сценарій припускає, що він має справу лише з об'єктами елементів. Крім того, for..inне гарантується відвідування об'єктів у певному порядку. for...ofцикли будуть циклічно обертати об'єкти NodeList.

Дивіться також:


3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Це має працювати, перетинати браузер і отримувати всі вузли "елемент".


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