Основные источники
- DOM Living Standart
- HTML Living Standart
- Document Object Model (DOM) Level 3 Core Specification
- DOM Parsing and Serialization
Введение
JavaScript
предоставляет множество методов для
работы с Document Object Model
или сокращенно
DOM
(объектной моделью документа): одни из них
являются более полезными, чем другие; одни используются часто,
другие почти никогда; одни являются относительно новыми, другие
признаны устаревшими.
Я постараюсь дать вам исчерпывающее представление об этих методах, а также покажу парочку полезных приемов, которые сделают вашу жизнь веб-разработчика немного легче.
Размышляя над подачей материала, я пришел к выводу, что оптимальным будет следование спецификациям с промежуточными и заключительными выводами, сопряженными с небольшими лирическими отступлениями.
Сильно погружаться в теорию мы не будем. Вместо этого, мы сосредоточимся на практической составляющей.
Для того, чтобы получить максимальную пользу от данной шпаргалки, пишите код вместе со мной и внимательно следите за тем, что происходит в консоли инструментов разработчика и на странице.
Вот как будет выглядеть наша начальная разметка:
<ul id="list" class="list"> <li id="item1" class="item">1</li> <li id="item2" class="item">2</li> <li id="item3" class="item">3</li></ul>
У нас есть список (ul
) с тремя элементами
(li
). Список и каждый элемент имеют идентификатор
(id
) и CSS-класс (class
). id
и class
это атрибуты элемента. Существует множество
других атрибутов: одни из них являются глобальными, т.е. могут
добавляться к любому элементу, другие локальными, т.е. могут
добавляться только к определенным элементам.
Мы часто будем выводить данные в консоль, поэтому создадим такую "утилиту":
const log = console.log
Миксин
NonElementParentNode
Данный миксин предназначен для обработки (браузером) родительских узлов, которые не являются элементами.
В чем разница между узлами (nodes) и элементами (elements)? Если кратко, то "узлы" это более общее понятие, чем "элементы". Узел может быть представлен элементом, текстом, комментарием и т.д. Элемент это узел, представленный разметкой (HTML-тегами (открывающим и закрывающим) или, соответственно, одним тегом).
У рассматриваемого миксина есть метод, наследуемый от объекта
Document
, с которого удобно начать разговор.
Небольшая оговорка: разумеется, мы могли бы создать список и элементы программным способом.
Для создания элементов используется метод
createElement(tag)
объекта Document
:
const listEl = document.createElement('ul')
Такой способ создания элементов называется императивным. Он
является не очень удобным и слишком многословным: создаем
родительский элемент, добавляет к нему атрибуты по одному, внедряем
его в DOM
, создаем первый дочерний элемент и т.д.
Следует отметить, что существует и другой, более изысканный способ
создания элементов шаблонные или строковые литералы (template
literals), но о них мы поговорим позже.
Одним из основных способов получения элемента (точнее, ссылки на
элемент) является метод getElementById(id)
объекта
Document
:
// получаем ссылку на наш списокconst listEl = document.getElementById('list')log(listEl)// ul#list.list - такая запись означает "элемент `ul` с `id === list`" и таким же `class`
Почему идентификаторы должны быть уникальными в пределах
приложения (страницы)? Потому что элемент с id
становится значением одноименного свойства глобального объекта
window
:
log(listEl === window.list) // true
Как мы знаем, при обращении к свойствам и методам
window
, слово window
можно опускать,
например, вместо window.localStorage
можно писать
просто localStorage
. Следовательно, для доступа к
элементу с id
достаточно обратиться к соответствующему
свойству window
:
log(list) // ul#list.list
Обратите внимание, что это не работает в React
и
других фреймворках, абстрагирующих работу с DOM
,
например, с помощью Virtual DOM
. Более того, там
иногда невозможно обратиться к нативным свойствам и методам
window
без window
.
Миксин ParentNode
Данный миксин предназначен для обработки родительских элементов (предков), т.е. элементов, содержащих одного и более потомка (дочерних элементов).
children
потомки элемента
const { children } = list // list.childrenlog(children)/*HTMLCollection(3) 0: li#item1.item 1: li#item2.item 2: li#item3.item length: 3*/
Такая структура называется
коллекцией HTML и представляет собой массивоподобный объект
(псевдомассив). Существует еще одна похожая структура
список узлов (NodeList
).
Массивоподобные объекты имеют свойство length
с
количеством потомков, метод forEach()
(NodeList
), позволяющий перебирать узлы (делать по ним
итерацию). Такие объекты позволяют получать элементы по индексу, по
названию (HTMLCollection
) и т.д. Однако, у них
отсутствуют методы настоящих массивов, такие как
map()
, filter()
, reduce()
и
др., что делает работу с ними не очень удобной. Поэтому
массивоподобные объекты рекомендуется преобразовывать в массивы с
помощью метода Array.from()
или spread-оператора:
const children = Array.from(list.children)// илиconst children = [...list.children]log(children) // [li#item1.item, li#item2.item, li#item3.item] - обычный массив
firstElementChild
первый потомок элементlastElementChild
последний потомок элемент
log(list.firstElementChild) // li#item1.itemlog(list.lastElementChild) // li#item2.item
Для дальнейших манипуляций нам потребуется периодически создавать новые элементы, поэтому создадим еще одну утилиту:
const createEl = (id, text, tag = 'li', _class = 'item') => { const el = document.createElement(tag) el.id = id el.className = _class el.textContent = text return el}
Наша утилита принимает 4 аргумента: идентификатор, текст, название тега и CSS-класс. 2 аргумента (тег и класс) имеют значения по умолчанию. Функция возвращает готовый к работе элемент. Впоследствии, мы реализует более универсальный вариант данной утилиты.
prepend(newNode)
добавляет элемент в начало спискаappend(newNode)
добавляет элемент в конец списка
// создаем новый элементconst newItem = createEl('item0', 0)// и добавляем его в начало спискаlist.prepend(newItem)// создаем еще один элементconst newItem2 = createEl('item4', 4)// и добавляем его в конец спискаlist.append(newItem2)log(children)/*HTMLCollection(5) 0: li#item0.item 1: li#item1.item 2: li#item2.item 3: li#item3.item 4: li#item4.item*/
Одной из интересных особенностей HTMLCollection
является то, что она является "живой", т.е. элементы, возвращаемые
по ссылке, и их количество обновляются автоматически. Однако, эту
особенность нельзя использовать, например, для автоматического
добавления обработчиков событий.
replaceChildren(nodes)
заменяет потомков новыми элементами
const newItems = [newItem, newItem2]// заменяем потомков новыми элементамиlist.replaceChildren(...newItems) // list.replaceChildren(newItem, newItem2)log(children) // 2
Наиболее универсальными способами получения ссылок на элементы
являются методы querySelector(selector)
и
querySelectorAll(selector)
. Причем, в отличие от
getElementById()
, они могут вызываться на любом
родительском элементе, а не только на document
. В
качестве аргумента названным методам передается любой валидный
CSS-селектор (id
, class
, tag
и т.д.):
// получаем элемент `li` с `id === item0`const itemWithId0 = list.querySelector('#item0')log(itemWithId0) // li#item0.item// получаем все элементы `li` с `class === item`const allItems = list.querySelectorAll('.item')log(allItems) // массивоподобный объект/*NodeList(2) 0: li#item0.item 1: li#item4.item length: 2*/
Создадим универсальную утилиту для получения элементов:
const getEl = (selector, parent = document, single = true) => single ? parent.querySelector(selector) : [...parent.querySelectorAll(selector)]
Наша утилита принимает 3 аргумента: CSS-селектор, родительский элемент и индикатор количества элементов (один или все). 2 аргумента (предок и индикатор) имеют значения по умолчанию. Функция возвращает либо один, либо все элементы (в виде обычного массива), совпадающие с селектором, в зависимости от значения индикатора:
const itemWithId0 = getEl('#item0', list)log(itemWithId0) // li#item0.itemconst allItems = getEl('.item', list, false)log(allItems) // [li#item0.item, li#item4.item]
Миксин
NonDocumentTypeChildNode
Данный миксин предназначен для обработки дочерних узлов, которые
не являются документом, т.е. всех узлов, кроме
document
.
previousElementSibling
предыдущий элементnextElementSibling
следующий элемент
log(itemWithId0.previousElementSibling) // nulllog(itemWithId0.nextElementSibling) // #item4
Миксин ChildNode
Данный миксин предназначен для обработки дочерних элементов, т.е. элементов, являющихся потомками других элементов.
before(newNode)
вставляет новый элемент перед текущимafter(newNode)
вставляет новый элемент после текущего
// получаем `li` с `id === item4`const itemWithId4 = getEl('#item4', list)// создаем новый элементconst newItem3 = createEl('item3', 3)// и вставляем его перед `itemWithId4`itemWithId4.before(newItem3)// создаем еще один элементconst newItem4 = createEl('item2', 2)// и вставляем его после `itemWithId0`itemWithId0.after(newItem4)
replaceWith(newNode)
заменяет текущий элемент новым
// создаем новый элементconst newItem5 = createEl('item1', 1)// и заменяем им `itemWithId0`itemWithId0.replaceWith(newItem5)
remove()
удаляет текущий элемент
itemWithId4.remove()
Интерфейс Node
Данный интерфейс предназначен для обработки узлов.
nodeType
тип узла
log(list.nodeType) // 1// другие варианты/* 1 -> ELEMENT_NODE (элемент) 3 -> TEXT_NODE (текст) 8 -> COMMENT_NODE (комментарий) 9 -> DOCUMENT_NODE (document) 10 -> DOCUMENT_TYPE_NODE (doctype) 11 -> DOCUMENT_FRAGMENT_NODE (фрагмент) и т.д.*/
nodeName
название узла
log(list.nodeName) // UL// другие варианты/* - квалифицированное название HTML-элемента прописными (заглавными) буквами - квалифицированное название атрибута - #text - #comment - #document - название doctype - #document-fragment*/
baseURI
основной путь
log(list.baseURI) // .../dom/index.html
parentNode
родительский узелparentElement
родительский элемент
const itemWithId1 = getEl('#item1', list)log(itemWithId1.parentNode) // #listlog(itemWithId1.parentElement) // #list
hasChildNodes()
возвращаетtrue
, если элемент имеет хотя бы одного потомкаchildNodes
дочерние узлы
log(list.hasChildNodes()) // truelog(list.childNodes)/*NodeList(3) 0: li#item1.item 1: li#item2.item 2: li#item3.item*/
firstChild
первый потомок узелlastChild
последний потомок узел
log(list.firstChild) // #item1log(list.lastChild) // #item3
nextSibling
следующий узелpreviousSibling
предыдущий узел
log(itemWithId1.nextSibling) // #item2log(itemWithId1.previousSibling) // null
textContent
геттер/сеттер для извлечения/записи текста
// получаем текстlog(itemWithId1.textContent) // 1// меняем текстitemWithId1.textContent = 'item1'log(itemWithId1.textContent) // item1// получаем текстовое содержимое всех потомковlog(list.textContent) // item123
Для извлечения/записи текста существует еще один (устаревший)
геттер/сеттер innerText
.
cloneNode(deep)
копирует узел. Принимает логическое значение, определяющее характер копирования: поверхностное копируется только сам узел, глубокое копируется сам узел и все его потомки
// создаем новый список посредством копирования существующегоconst newList = list.cloneNode(false)// удаляем у него `id` во избежание коллизийnewList.removeAttribute('id')// меняем его текстовое содержимоеnewList.textContent = 'new list'// и вставляем его после существующего спискаlist.after(newList)// создаем еще один списокconst newList2 = newList.cloneNode(true)newList.after(newList2)
isEqualNode(node)
сравнивает узлыisSameNode(node)
определяет идентичность узлов
log(newList.isEqualNode(newList2)) // truelog(newList.isSameNode(newList2)) // false
contains(node)
возвращаетtrue
, если элемент содержит указанный узел
log(list.contains(itemWithId1)) // true
insertBefore(newNode, existingNode)
добавляет новый узел (newNode
) перед существующим (existingNode
)
// создаем новый элементconst itemWithIdA = createEl('#item_a', 'a')// и вставляем его перед `itemWithId1`list.insertBefore(itemWithIdA, itemWithId1)
appendChild(node)
добавляет узел в конец списка
// создаем новый элементconst itemWithIdC = createEl('#item_c', 'c')// и добавляем его в конец спискаlist.appendChild(itemWithIdC)
replaceChild(newNode, existingNode)
заменяет существующий узел (existingNode
) новым (newNode
):
// создаем новый элементconst itemWithIdB = createEl('item_b', 'b')// и заменяем им `itemWithId1`list.replaceChild(itemWithIdB, itemWithId1)
removeChild(node)
удаляет указанный дочерний узел
// получаем `li` с `id === item2`const itemWithId2 = getEl('#item2', list)// и удаляем егоlist.removeChild(itemWithId2)
Интерфейс Document
Данный интерфейс предназначен для обработки объекта
Document
.
URL
иdocumentURI
адрес документа
log(document.URL) // .../dom/index.htmllog(document.documentURI) // ^
documentElement
:
log(document.documentElement) // html
getElementsByTagName(tag)
возвращает все элементы с указанным тегом
const itemsByTagName = document.getElementsByTagName('li')log(itemsByTagName)/*HTMLCollection(4) 0: li##item_a.item 1: li#item_b.item 2: li#item3.item 3: li##item_c.item*/
getElementsByClassName(className)
возвращает все элементы с указанным CSS-классом
const itemsByClassName = list.getElementsByClassName('item')log(itemsByClassName) // ^
createDocumentFragment()
возвращает фрагмент документа:
// создаем фрагментconst fragment = document.createDocumentFragment()// создаем новый элементconst itemWithIdD = createEl('item_d', 'd')// добавляем элемент во фрагментfragment.append(itemWithIdD)// добавляем фрагмент в списокlist.append(fragment)
Фрагменты позволяют избежать создания лишних элементов. Они
часто используются при работе с разметкой, скрытой от пользователя
с помощью тега template
(метод
cloneNode()
возвращает
DocumentFragment
).
-
createTextNode(data)
создает текст -
createComment(data)
создает комментарий -
importNode(existingNode, deep)
создает новый узел на основе существующего
// создаем новый список на основе существующегоconst newList3 = document.importNode(list, true)// вставляем его перед существующим спискомlist.before(newList3)// и удаляем во избежание коллизийnewList3.remove()
createAttribute(attr)
создает указанный атрибут
Интерфейсы
NodeIterator
и TreeWalker
Интерфейсы
NodeIterator
и
TreeWalker
предназначены для обхода (traverse)
деревьев узлов. Я не сталкивался с примерами их практического
использования, поэтому ограничусь парочкой примеров:
// createNodeIterator(root, referenceNode, pointerBeforeReferenceNode, whatToShow, filter)const iterator = document.createNodeIterator(list)log(iterator)log(iterator.nextNode()) // #listlog(iterator.nextNode()) // #item_alog(iterator.previousNode()) // #item_alog(iterator.previousNode()) // #listlog(iterator.previousNode()) // null// createTreeWalker(root, whatToShow, filter)// применяем фильтры - https://dom.spec.whatwg.org/#interface-nodefilterconst walker = document.createTreeWalker(list, '0x1', { acceptNode: () => 1 })log(walker)log(walker.parentNode()) // nulllog(walker.firstChild()) // #item_alog(walker.lastChild()) // nulllog(walker.previousSibling()) // nulllog(walker.nextSibling()) // #item_blog(walker.nextNode()) // #item3log(walker.previousNode()) // #item_b
Интерфейс Element
Данный интерфейс предназначен для обработки элементов.
localName
иtagName
название тега
log(list.localName) // ullog(list.tagName) // UL
id
геттер/сеттер для идентификатораclassName
геттер/сеттер для CSS-класса
log(list.id) // listlist.id = 'LIST'log(LIST.className) // list
classList
все CSS-классы элемента (объектDOMTokenList
)
const button = createEl('button', 'Click me', 'my_button', 'btn btn-primary')log(button.classList)/*DOMTokenList(2) 0: "btn" 1: "btn-primary" length: 2 value: "btn btn-primary"*/
Работа с classList
classList.add(newClass)
добавляет новый класс к существующимclassList.remove(existingClass)
удаляет указанный классclassList.toggle(className, force?)
удаляет существующий класс или добавляет новый. Если опциональный аргументforce
имеет значениеtrue
, данный метод только добавляет новый класс при отсутствии, но не удаляет существующий класс (в этом случаеtoggle() === add()
). Еслиforce
имеет значениеfalse
, данный метод только удаляет существующий класс при наличии, но не добавляет отсутствующий класс (в этом случаеtoggle() === remove()
)classList.replace(existingClass, newClass)
заменяет существующий класс (existingClass
) на новый (newClass
)classList.contains(className)
возвращаетtrue
, если указанный класс обнаружен в списке классов элемента (данный метод идентиченclassName.includes(className)
)
// добавляем к кнопке новый классbutton.classList.add('btn-lg')// удаляем существующий классbutton.classList.remove('btn-primary')// у кнопки есть класс `btn-lg`, поэтому он удаляетсяbutton.classList.toggle('btn-lg')// заменяем существующий класс на новыйbutton.classList.replace('btn', 'btn-success')log(button.className) // btn-successlog(button.classList.contains('btn')) // falselog(button.className.includes('btn-success')) // true
Работа с атрибутами
hasAttributes()
возвращаетtrue
, если у элемента имеются какие-либо атрибутыgetAttributesNames()
возвращает названия атрибутов элементаgetAttribute(attrName)
возвращает значение указанного атрибутаsetAttribute(name, value)
добавляет указанные атрибут и его значение к элементуremoveAttribute(attrName)
удаляет указанный атрибутhasAttribute(attrName)
возвращаетtrue
при наличии у элемента указанного атрибутаtoggleAttribute(name, force)
добавляет новый атрибут при отсутствии или удаляет существующий атрибут. Аргументforce
аналогичен одноименному атрибутуclassList.toggle()
log(button.hasAttributes()) // truelog(button.getAttributeNames()) // ['id', 'class']log(button.getAttribute('id')) // buttonbutton.setAttribute('type', 'button')button.removeAttribute('class')log(button.hasAttribute('class')) // false
В использовании перечисленных методов для работы с атрибутами
нет особой необходимости, поскольку многие атрибуты являются
геттерами/сеттерами, т.е. позволяют извлекать/записывать значения
напрямую. Единственным исключением является метод
removeAttribute()
, поскольку существуют атрибуты без
значений: например, если кнопка имеет атрибут
disabled
, установка значения данного атрибута в
false
не приведет к снятию блокировки для этого нужно
полностью удалить атрибут disabled
с помощью
removeAttribute()
.
Отдельного упоминания заслуживает атрибут data-*
,
где символ *
означает любую строку. Он предназначен
для определения пользовательских атрибутов. Например, в нашей
начальной разметке для уникальной идентификации элементов
используется атрибут id
. Однако, это приводит к
загрязнению глобального пространства имен, что чревато коллизиями
между нашими переменными и, например, переменными используемой нами
библиотеки когда какой-либо объект библиотеки пытается записаться в
свойство window
, которое уже занято нашим
id
.
Вместо этого, мы могли бы использовать атрибут
data-id
и получать ссылки на элементы с помощью
getEl('[data-id="id"]')
.
Название data-атрибута после символа -
становится
одноименным свойством объекта dataset
. Например,
значение атрибута data-id
можно получить через
свойство dataset.id
.
closest(selectors)
возвращает первый родительский элемент, совпавший с селекторами
LIST.append(button)log(button.closest('#LIST', 'document.body')) // #LIST
matches(selectors)
возвращаетtrue
, если элемент совпадает хотя бы с одним селектором
log(button.matches('.btn', '[type="button"]'))// у кнопки нет класса `btn`, но есть атрибут `type` со значением `button`,// поэтому возвращается `true`
-
insertAdjacentElement(where, newElement)
универсальный метод для вставки новых элементов перед/в начало/в конец/после текущего элемента. Аргументwhere
определяет место вставки. Возможные значения:
beforebegin
перед открывающим тегомafterbegin
после открывающего тегаbeforeend
перед закрывающим тегомafterend
после закрывающего тега
-
insertAdjacentText(where, data)
универсальный метод для вставки текста -
Text
конструктор для создания текста -
Comment
конструктор для создания комментария
const text = new Text('JavaScript')log(text) // "JavaScript"const part = text.splitText(4)log(part) // "Script"log(part.wholeText()) // Scriptconst comment = new Comment('TODO')log(comment) // <!--TODO-->
Объект Document
location
объект с информацией о текущей локации документа
log(document.location)
Свойства объекта location
:
hash
хэш-часть URL (символ#
и все, что следует за ним), например,#top
host
название хоста и порт, например,localhost:3000
hostname
название хоста, например,localhost
href
полный путьorigin
protocol
+host
pathname
путь без протоколаport
порт, например,3000
protocol
протокол, например,https
search
строка запроса (символ?
и все, что следует за ним), например,?name=John&age=30
Методы location
:
-
reload()
перезагружает текущую локацию -
replace()
заменяет текущую локацию на новую -
title
заголовок документа
log(document.title) // DOM
-
head
метаданные документа -
body
тело документа -
images
псевдомассив (HTMLCollection
), содержащий все изображения, имеющиеся в документе
const image = document.createElement('img')image.className = 'my_image'image.src = 'https://miro.medium.com/max/875/1*ZIH_wjqDfZn6NRKsDi9mvA.png'image.alt = "V8's compiler pipeline"image.width = 480document.body.append(image)log(document.images[0]) // .my_image
links
псевдомассив, содержащий все ссылки, имеющиеся в документе
const link = document.createElement('a')link.className = 'my_link'link.href = 'https://github.com/azat-io/you-dont-know-js-ru'link.target = '_blank'link.rel = 'noopener noreferrer'link.textContent = 'Вы не знаете JS'document.body.append(link)log(document.links[0]) // .my_link
forms
псевдомассив, содержащий все формы, имеющиеся в документе
const form = document.createElement('form')form.className = 'my_form'document.body.append(form)log(document.forms[0]) // .my_form
Следующие методы и свойство считаются устаревшими:
open()
открывает документ для записи. При этом документ полностью очищаетсяclose()
закрывает документ для записиwrite()
записывает данные (текст, разметку) в документwriteln()
записывает данные в документ с переносом на новую строкуdesignMode
управление режимом редактирования документа. Возможные значения:on
иoff
. Наберитеdocument.designMode = 'on'
в консолиDevTools
и нажмитеEnter
. Вуаля, страница стала редактируемой: можно удалять/добавлять текст, перетаскивать изображения и т.д.execCommand()
выполняет переданные команды. Со списоком доступных команд можно ознакомиться здесь. Раньше этот метод активно использовался для записи/извлечения данных из буфера обмена (командыcopy
иpaste
). Сейчас для этого используются методыnavigator.clipboard.writeText()
,navigator.clipboard.readText()
и др.
Миксин InnerHTML
Геттер/сеттер innerHTML
позволяет
извлекать/записывать разметку в элемент. Для подготовки разметки
удобно пользоваться шаблонными литералами:
const itemsTemplate = ` <li data-id="item1" class="item">1</li> <li data-id="item2" class="item">2</li> <li data-id="item3" class="item">3</li>`LIST.innerHTML = itemsTemplatelog(LIST.innerHTML)/*<li data-id="item1" class="item">1</li><li data-id="item2" class="item">2</li><li data-id="item3" class="item">3</li>*/
Расширения интерфейса
Element
outerHTML
геттер/сеттер для извлечения/записи внешней разметки элемента: то, что возвращаетinnerHTML
+ разметка самого элемента
log(LIST.outerHTML)/*<ul id="LIST" class="list"> <li data-id="item1" class="item">1</li> <li data-id="item2" class="item">2</li> <li data-id="item3" class="item">3</li></ul>*/
insertAdjacentHTML(where, string)
универсальный метод для вставки разметки в виде строки. Аргументwhere
аналогичен одноименному аргументу методаinsertAdjacentElement()
Метод insertAdjacentHTML()
в сочетании с шаблонными
литералами и их продвинутой версией тегированными шаблонными
литералами (tagged template literals) предоставляет много
интересных возможностей по манипулированию разметкой документа. По
сути, данный метод представляет собой движок шаблонов (template
engine) на стороне клиента, похожий на Pug
,
Handlebars
и др. серверные движки. С его помощью (при
участии History API
) можно, например, реализовать
полноценное одностраничное приложение (Single Page
Application
или сокращенно SPA
). Разумеется,
для этого придется написать чуть больше кода, чем при использовании
какого-либо фронтенд-фреймворка.
Вот несколько полезных ссылок, с которых можно начать изучение этих замечательных инструментов:
- Element.insertAdjacentHTML() MDN
- Изменение документа JSR
- Шаблонные строки MDN
- Template Literals ECMAScript 2022
Иногда требуется создать элемент на основе шаблонной строки. Как это можно сделать? Вот соответствующая утилита:
const createElFromStr = (str) => { // создаем временный элемент const el = document.createElement('div') // записываем в него переданную строку - разметку el.innerHTML = str // извлекаем наш элемент // если мы используем здесь метод `firstChild()`, может вернуться `#text` // одна из проблем шаблонных строк заключается в большом количестве лишних пробелов const child = el.fisrtElementChild // удаляем временный элемент el.remove() // и возвращаем наш элемент return child}// шаблон спискаconst listTemplate = `<ul id="list"> <li data-id="item1" class="item">1</li> <li data-id="item2" class="item">2</li> <li data-id="item3" class="item">3</li></ul>`// создаем список на основе шаблонаconst listEl = createElFromStr(listTemplate)// и вставляем его в тело документаdocument.body.append(listEl)
Существует более экзотический способ создания элемента на основе
шаблонной строки. Он предполагает использование конструктора
DOMParser()
:
const createElFromStr = (str) => { // создаем новый парсер const parser = new DOMParser() // парсер возвращает новый документ const { body: { children } } = parser.parseFromString(str, 'text/html') // нас интересует первый дочерний элемент тела нового документа return children[0]}const listTemplate = `<ul id="list"> <li data-id="item1" class="item">1</li> <li data-id="item2" class="item">2</li> <li data-id="item3" class="item">3</li></ul>`const listEl = createElFromStr(listTemplate)document.body.append(listEl)
Еще более экзотический, но при этом самый короткий способ
предполагает использование
расширения для объекта Range
метода
createContextualFragment()
:
const createElFromStr = (str) => { // создаем новый диапазон const range = new Range() // создаем фрагмент const fragment = range.createContextualFragment(str) // и возвращаем его return fragment}// или в одну строкуconst createFragment = (str) => new Range().createContextualFragment(str)const listTemplate = `<ul id="list"> <li data-id="item1" class="item">1</li> <li data-id="item2" class="item">2</li> <li data-id="item3" class="item">3</li></ul>`document.body.append(createFragment(listTemplate))
В завершение, как и обещал, универсальная утилита для создания элементов:
// функция принимает название тега и объект с настройкамиconst createEl = (tag, opts) => { const el = document.createElement(tag) // перебираем ключи объекта и записывает соответствующие свойства в элемент for (const key in opts) { el[key] = opts[key] } // возвращаем готовый элемент return el}const button = createEl('button', { // настройками могут быть атрибуты id: 'my_button', className: 'btn btn-primary', textContent: 'Click me', title: 'My button', autofocus: true, // стили style: 'color: red; cursor: pointer;', // обработчики и т.д. onmouseenter: function () { this.style.color = 'green' }, onmouseout: function () { this.style.color = 'blue' }, onclick: () => alert('Привет!')})document.body.append(button)
Заключение
Современный JS
предоставляет богатый арсенал
методов для работы с DOM
. Данных методов вполне
достаточно для решения всего спектра задач, возникающих при
разработке веб-приложений. Хорошее знание этих методов, а также
умение их правильно применять гарантируют не только высокое
качество (чистоту) кода, но также избавляют от необходимости
использовать DOM-библиотеки (такие как jQuery
), что в
совокупности обусловливает производительность приложения, его
поддерживаемость и масштабируемость.
Разумеется, шпаргалка это всего лишь карманный справочник, памятка для быстрого восстановления забытого материала, предполагающая наличие определенного багажа знаний.
VDS от Маклауд быстрые и безопасные.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!