Не паси задних используй DOM как профессионал

Не паси задних используй DOM как профессионал

Когда я начал карьеру профессионального веб-разработчика в 2008 году, то знал понемногу HTML, CSS и PHP. А также изучал JavaScript, потому что с помощью него я показывал и скрывал элементы, делал классные вещи, такие как выпадающие меню.

Тогда я работал в небольшой компании, которая создавала системы CMS для клиентов, и потребовался загрузчик нескольких файлов. То, что невозможно сделать в то время с нативным JavaScript.

После поисков я нашёл забавное решение на основе Flash и библиотеки JavaScript под названием MooTools. Там есть классная функция $ для выбора элементов DOM и модули, такие как индикаторы выполнения и запросы Ajax. Две недели спустя я открыл для себя jQuery и поразился.

Нет многословных, неуклюжих манипуляций с DOM, только простые связанные селекторы и масса полезных плагинов.

Перенесёмся в 2019 год, где миром правят фреймворки. Если ты начал путь веб-разработчика в последнее десятилетие, велика вероятность, что в будущем не столкнёшься с «сырым» DOM. Возможно, тебе это и не нужно.

Хотя такие фреймворки, как Angular и React, привели к стремительному спаду популярности jQuery, он по-прежнему используется ошеломляющими 66 миллионами веб-сайтов. По оценкам это составляет 74% всех сайтов в мире.

Наследие jQuery впечатляет, и показательный пример его влияния на стандарты – методы querySelector и querySelectorAll , которые имитируют функцию jQuery $ .

По иронии судьбы эти два метода стали главной причиной снижения популярности jQuery, поскольку заменили наиболее используемую функциональность jQuery: простой выбор элементов DOM.

Но чистый DOM API многословен.

Я имею в виду $ в сравнении с document.querySelectorAll .

И это то, что мешает разработчикам использовать нативный DOM API. Но здесь на самом деле нет необходимости.

Нативный DOM API великолепен и чрезвычайно полезен. Да, многословен, но это потому, что предоставляет низкоуровневые блоки для построения абстракций. И если переживаешь насчёт дополнительных нажатий клавиш, расслабься: у всех современных редакторов и IDE превосходное автозавершение кода. Ещё вариант: создавай псевдонимы наиболее часто используемых функций.

Выбор элементов

Один элемент

Чтобы выбрать один элемент с использованием любого допустимого селектора CSS, напиши:

Подставляй в код любой селектор:

Когда нет соответствующих элементов, возвращается null .

Несколько элементов

Для выбора нескольких элементов используй:

Применяй document.querySelectorAll , как и document.querySelector . Подойдёт любой действительный селектор CSS, и единственное отличие: querySelector вернёт один элемент, а querySelectorAll – статический NodeList с найденными элементами. Если не найдено ни одного элемента, получишь пустой NodeList .

A NodeList – итерируемый объект, похожий на массив, но не массив, поэтому у него нет тех же методов. Ты запустишь forEach на нём, но не map , reduce или find .

Если для этого объекта понадобились методы массива, то преобразуй его в массив с помощью деструктуризации или Array.from :

querySelectorAll отличается от методов, наподобие getElementsByTagName и getElementsByClassName тем, что они возвращают динамическую коллекцию HTMLCollection , а querySelectorAll – статический NodeList .

И если сделать getElementsByTagName(‘p’) и убрать один <p> из документа, он удаляется также из полученной HTMLCollection .

Но если напишешь querySelectorAll(‘p’) и удалишь один <p> из документа, он останется в полученном NodeList .

Другое отличие состоит в том, что HTMLCollection содержит элементы HTMLElement , а NodeList – любой тип Node .

Относительные поиски

Нет необходимости вызывать querySelector(All) с document . Запускай относительный поиск на любом HTMLElement :

Но это по-прежнему многословно!

Если ты всё ещё беспокоишься о дополнительных нажатиях клавиш, применяй оба:

Поднимаемся по дереву DOM

Использование CSS-селекторов для выбора DOM-элементов означает, что мы спускаемся по дереву DOM. Нет CSS-селекторов для движения вверх и выбора родителей.

Но перемещаться вверх по дереву DOM можно с помощью closest() , который также принимает любой валидный CSS-селектор:

Это найдёт ближайший родительский элемент абзаца, выбранного выражением document.querySelector(‘p’) . Свяжи эти вызовы, чтобы подняться выше по дереву:

Добавление элементов

Код для добавления одного или нескольких элементов в дерево DOM пользуется дурной славой за счёт быстрого нагромождения словарных конструкций. Чтобы добавить следующую ссылку на страницу:

Представь, что придётся сделать это для 10 элементов.

А в jQuery получится так:

А знаешь что? Есть нативный эквивалент:

Метод insertAdjacentHTML вставляет произвольную действительную HTML-строку в DOM на четыре позиции, указанные в первом параметре:

  1. ‘beforebegin’ : перед элементом.
  2. ‘afterbegin’ : внутри элемента перед его первым потомком.
  3. ‘beforeend’ : внутри элемента после его последнего потомка.
  4. ‘afterend’ : после элемента .

Это также упрощает указание точного места для вставки нового элемента. Допустим, нужен <a> прямо перед этим <p> . Без insertAdjacentHTML сделали бы:

Теперь хватит этого:

Здесь также не обошлось без эквивалентного метода для вставки элементов DOM:

Двигаем элементы

Метод insertAdjacentElement также используется для перетасовки существующих элементов в том же документе. Когда элемент, который вставляем с помощью insertAdjacentElement , уже присутствует в документе, он просто перемещается.

Когда такой HTML:

и <h2> вставляется после <h1> :

он просто сдвигается, а не копируется:

Заменяем элементы

Элемент DOM заменяется любым другим элементом DOM с использованием метода replaceWith :

Заменой выступает новый элемент, созданный с помощью document.createElement , или элемент, который уже есть в том же документе (тогда он снова будет перемещён, а не скопирован):

Удаляем элементы

Просто вызывай у элемента метод remove :

Лучше, чем по-старому:

Создаём элемент из сырого HTML

insertAdjacentHTML вставляет сырой HTML в документ, но что если хотим создать элемент из сырого HTML и использовать его позже?

Для этого понадобится объект DomParser и метод parseFromString . DomParser преобразует исходный код HTML или XML в документ DOM. Используем метод parseFromString для создания документа с одним элементом и возвращаем только этот элемент:

Инспектируем DOM

Стандартный DOM API также предоставляет методы для инспекции DOM. Например, matches проверяет соответствие элемента определённому селектору:

А также проверим, будет ли элемент дочерним по отношению к другому элементу с помощью contains :

Чтобы получить больше информации об элементах, используй compareDocumentPosition . Этот метод определяет, предшествует ли один элемент другому элементу или следует за ним, или же один из них содержит другой. Возвращает целое число, которое представляет собой отношение между сравниваемыми элементами.

Вот пример с элементами из предыдущего фрагмента:

Возвращаемое значение compareDocumentPosition – целое число, биты которого представляют собой отношение между узлами касательно аргумента, указанного в этом методе.

Таким образом, с учётом синтаксиса node.compareDocumentPostion(otherNode) возвращаемое число означает:

  1. 1: узлы не части одного и того же документа.
  2. 2: otherNode предшествует node
  3. 4: otherNode следует за node .
  4. 8: otherNode содержит node .
  5. 16: otherNode содержится в node .

Поскольку устанавливается не один бит, в приведённом выше примере container.compareDocumenPosition(h1) возвращает 20, когда ожидалось 16, ведь h1 содержится в container . Но h1 также следует за элементом container (4), поэтому полученное значение равно 16 + 4 = 20.

Больше подробностей, пожалуйста!

Чтобы отслеживать изменения в любом узле DOM, используй интерфейс MutationObserver . Это включает в себя изменения текста, добавление или удаление узлов из наблюдаемого элемента или изменение атрибутов узла.

MutationObserver – невероятно мощный API для наблюдения практически за любыми изменениями, которые происходят в элементе DOM и его дочерних узлах.

Новый MutationObserver создаётся путём вызова конструктора с функцией обратного вызова. Этот обратный вызов будет запускаться всякий раз, когда изменяется наблюдаемый узел:

Чтобы отслеживать элемент, вызываем метод наблюдателя observe , где исследуемый узел будет первым аргументом и объект с параметрами – вторым.

Наблюдение начинается после вызова observe . Объект параметров принимает следующие ключи:

  1. attributes : когда значение true , изменения атрибутов узла будут отслеживаться.
  2. attributeFilter : массив имён атрибутов для мониторинга, когда attributes равно true , а этот ключ не задан, наблюдаются изменения всех атрибутов узла.
  3. attributeOldValue : при установке true записывается предыдущее значение атрибута при каждом изменении.
  4. characterData : когда значение true , регистрирует изменения текста текстового узла, так что подходит для элементов Text , а не HTMLElement . Чтобы это работало, узел должен быть объектом Text или, если наблюдатель отслеживает HTMLElement , требуется значение true у параметра subtree для мониторинга изменений дочерних узлов.
  5. characterDataOldValue : если true , регистрируется предыдущее значение символьных данных при каждом изменении.
  6. subtree : когда true , также отслеживаются изменения дочерних узлов наблюдаемого элемента.
  7. childList : установи true , чтобы контролировать добавление и удаление дочерних узлов элемента. Если subtree задано значение true , для дочерних элементов также отслеживаются удаление и добавление дочерних узлов.

Когда начинаешь мониторинг элемента при запуске observe , обратный вызов в конструкторе MutationObserver вызывается с массивом объектов MutationRecord , описывающих произошедшие изменения, и наблюдателем в качестве второго параметра.

A MutationRecord содержит следующие свойства:

  1. type : тип изменения, attributes , characterData либо childList .
  2. target : изменённый элемент: его атрибуты, символьные данные или дочерние элементы.
  3. addedNodes : список добавленных узлов или пустой NodeList , если ничего не добавлялось.
  4. removedNodes : список удалённых узлов или пустой NodeList , если ничего не удалялось.
  5. attributeName : имя изменённого атрибута или null , если атрибуты не изменялись.
  6. previousSibling : предыдущий смежный элемент добавленных или удалённых узлов или null .
  7. nextSibling : следующий смежный элемент добавленных или удалённых узлов или null .

Допустим, будем наблюдать изменения в атрибутах и ​​дочерних узлах:

Когда завершаем мониторинг, отключаем наблюдателя и при необходимости вызываем метод takeRecords для получения незавершённых изменений, которые ещё не доставлены в обратный вызов:

Не бойся DOM

DOM API потрясает мощностью и универсальностью, хотя и многословен. Помни о его предназначении предоставлять разработчикам низкоуровневые строительные блоки для создания абстракций. В этом смысле он требует многословности, чтобы гарантировать однозначный и понятный API.

Пусть дополнительные нажатия клавиш не отпугивают от использования полного потенциала этого интерфейса.

DOM – необходимое знание для каждого JavaScript разработчика, поскольку, скорее всего, используешь его каждый день. Не бойся этого, используй в полной мере.