Как преобразовать список узлов DOM в массив в Javascript?

101

У меня есть функция 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, но это действительно было бы моим решением, я думаю, вы правильно поняли.
Кристоффер Салл-Сторгаард,
> for (i = 0; i & lt; x.length; i ++) Зачем получать длину списка узлов на каждой итерации? Это не только пустая трата времени, но, поскольку NodeLists - это живые коллекции, если что-либо в теле цикла изменит его длину, вы можете зацикливаться бесконечно или выйти за пределы индекса. Последнее - худшее, что может произойти, если вы присвоите длину переменной, и ошибка намного лучше, чем бесконечный цикл.
Это действительно старый вопрос, но jQuery был построен с использованием метода .noConflict специально, чтобы он не вызвал конфликта с другими библиотеками (даже с самим собой), что означает, что на страницу можно загрузить несколько версий jQuery. Тем не менее, лучше избегать использования / загрузки библиотеки, если в этом нет крайней необходимости.
vol7ron
@ vol7ron: перенесемся в 2016 год, и все по-прежнему обеспокоены размером, который библиотеки javascript добавляют на страницу. Конечно, размер JQuery minified и gzipped составляет 30 КБ, это все еще 30 КБ слишком много, чтобы преобразовать список узлов :-)
Guss

Ответы:

68

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])?
Тим Даун
@ Тим, да, у меня было такое раньше, но вчера вечером редактировал, не замечая этого (3 часа ночи по местному времени :), спасибо !.
Кристиан С. Сальвадо,
9
В каких обстоятельствах могло бы obj.lengthбыть что-то другое, кроме целочисленного значения?
Питер
1
Не могу поверить, что это так сложно. Некрасиво. Это очень распространенная потребность в программировании Web / JS. Новый метод для следующего выпуска языка?
Эндрю Копер
1
@AlbertoPerez, пожалуйста! Saludos hasta Madrid!
Christian C. Salvadó
132

В es6 вы можете просто использовать следующее:

  • Оператор распространения

     var elements = [... nodelist]
    
  • С помощью Array.from

     var elements = Array.from(nodelist)
    

дополнительная информация на https://developer.mozilla.org/en-US/docs/Web/API/NodeList

Camiloazula
источник
4
так легко с Array.from(): D
Josan Iracheta
4
в случае, если кто-то использует этот подход с Typecript (для ES5), Array.fromработает только , поскольку TS передает это nodelist.slice- что не поддерживается.
Питер Альберт
Я ответил так же за год до того, как вы опередили меня по голосованию? Я не могу это объяснить ..
vsync 07
3
@vsync, в вашем ответе не упоминаетсяArray.from
ESR
@EdmundReed - так? как это оправдывает это. писать дольше, поэтому в реальной ситуации это никогда не будет использоваться, только spreadиспользуется.
vsync
17

Используя распространение (ES2015) , это так же просто, как:[...document.querySelectorAll('p')]

(необязательно: используйте Babel для преобразования вышеуказанного кода ES6 в синтаксис ES5)


Попробуйте в консоли своего браузера и убедитесь в волшебстве:

for( links of [...document.links] )
  console.log(links);
vsync
источник
По крайней мере, последний хром, 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()(«универсальный массив»), насколько мне известно, не стала стандартным поведением).

Бретт Замир
источник
Я обновил, чтобы указать, что спецификации могут двигаться в направлении требования такого поведения.
Бретт Замир,
5

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

В другом случае (если вы не можете использовать ES6) вы можете использовать кратчайший способ преобразовать a 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