Перевод “HTMLCollection, NodeList and array of objects”
Предположим, что имеем код DOM указанный ниже. Необходимо получить массив всех дочерних узлов div.container
с помощью JavaScript.
<div id=”container”>
<div class="divy">...</div>
<div class="divy">...</div>
<div class="divy">...</div>
<div class="divy">...</div>
</div>
Если вы любите jQuery, то вы могли бы предложить такой код:
$('.divy')
// или
$('#container').children()
Я пытался сделать это используя нативный селектор DOM и вот, что получилось:
const childDivs = document.querySelectorAll('.divy')
Array.isArray(childDivs) //=> false
childDivs.constructor.name //=> NodeList
Ну ладно. Давайте попробуем один из методов getElementBy%
и посмотрим, что он возвращает:
const childDivsAgain = document.getElementsByClassName('divy')
Array.isArray(childDivs) //=> false
childDivs.constructor.name //=> HTMLCollection
Что это за объектыNodeList
и HTMLCollection
и почему мы не получаем “ванильный” массив JavaScript с помощью этих методов?
Попробуем понять разницу между NodeList
и HTMLCollection
:
HTMLCollection
- это список узлов. Отдельный узел может быть доступен по порядковому номеру или имени узла и атрибута.
Коллекции в HTML DOM являются живыми. Коллекции обновляются при изменении документа.
Коллекция HTML всегда находится в DOM, в то время как NodeList
является более универсальной конструкцией, которая может или не может быть в DOM.
ОбъектNodeList
является коллекцией узлов.NodeList
обеспечивает уровень абстракции над коллекцией узлов, не определяя и не ограничивая как это коллекция используется. ОбъектыNodeList
являются “живыми” или статическим, в зависимости от способа используемого для их получения.
Рассмотрим код ниже:
let parentDiv = document.getElementById('container')
let nodeListDivs = document.querySelectorAll('.divy')
let htmlCollectionDivs = document.getElementsByClassName('divy')
nodeListDivs.length //=> 4
htmlCollectionDivs.length //=> 4
//добавим новый дочерний контейнер
var newDiv = document.createElement('div');
newDiv.className = 'divy'
parentDiv.appendChild(newDiv)
nodeListDivs.length //=> 4
htmlCollectionDivs.length //=> 5
Вы можете видеть, что HTMLCollection
учел появление нового узла.Так изменение в DOM автоматически доступны в коллекции.
Но не все объекты NodeList
являются статическими. Например, document.getElementByName
вернет нам “живой” NodeList
.
Но помните, что ни HTMLCollection,
ни NodeList
не поддерживают методы прототипа массива как push, pop
илиsplice
. Перебирающий методы, как forEach
, были добавлены недавно и уже поддерживаются в новейшими браузерами.
Эти знания мне понадобились, когда я пробовал использовать библиотеку dragula drag-n-drop. Там мне необходимо было добавить выбранные компоненты в библиотеку.
let draggableLists = document.querySelectorAll('div.draggable')
dragula(draggableLists); // твоюж! не работает!
Как и ожидалось, этот код не будет работать. Так как конструктор dragula()
ждет на вход массив, а получает NodeList
.
Чтобы преобразовать NodeList
или HTMLCollection
в массив, необходимо сделать одно из следующего:
const nodelist = document.querySelectorAll(‘.divy’)
const divyArray = Array.from(nodelist)
const nodelist = document.querySelectorAll(‘.divy’)
const divyArray = Array.prototype.slice.call(nodelist)
Использовать ES6
Мне нравится данный способ. Если вы используете ES6, то поможет spread
оператор:
const divyArray = […document.querySelectorAll(‘.divy’)]
Добавим этот код в dragula
и все заработает:
dragula([...document.querySelectorAll('div.draggable')])
Так когда необходимо преобразовывать NodeList
в массив? Это зависит от вашего конкретного случая. Если вы хотите использовать актуальную информацию об узлах в DOM все время, то вы должны использовать NodeList
или HTMLCollection
как есть, без преобразования в массив.