- Notifications
You must be signed in to change notification settings - Fork 47
DOM (Document Object Model) - объектная модель, которая используется для XML/HTML-документов.
DOM - это внутреннее представление HTML-страницы в виде дерева, иерархии.
Каждый HTML-тег образует узел дерева с типом «элемент». Вложенные в него теги становятся дочерними узлами.
DOM is a representation — a model of a document and its content. — w3.org
DOM - It’s a way of representing a structured document via objects. — vuejsfeed.com
Во время загрузки браузер читает документ и тут же строит из него DOM, по мере получения информации достраивая новые узлы и тут же отображая их. Этот процесс идет непрерывным потоком.
Всего различают 12 типов узлов, но на практике используют 4:
- Документ – точка входа в DOM.
- Элементы – основные строительные блоки.
- Текстовые узлы – содержат текст.
- Комментарии – содержат информацию, которая не будет показана, но доступна из JS.
Пример комментария:
<!-- комментарий -->
DOM нужен для того, чтобы манипулировать страницей – читать информацию из HTML, создавать и изменять элементы.
Если попытаться получить элемент, которого нет или пока нет в DOM, получим null.
К примеру, в <head> находится скрипт, в котором пытаемся получить доступ к <body>.
Дочерние элементы (или дети) – элементы, которые лежат непосредственно внутри данного.
Потомки – все элементы, которые лежат внутри данного, вместе с их детьми, детьми их детей и так далее. То есть, всё поддерево DOM.
DOM-коллекции (псевдомассивы) не являются JS-массивами (не имеют forEach, map и друшгих методов).
childNodes - коллекция, содержащая все дочерние узлы (включая текстовые).
children - коллекция, содержащая только дочерние узлы-элементы (соответствуют тегам).
querySelector('css')|querySelectorAll('css')- по css-селекторуgetElementByClassName('classname')|getElementsByClassName('classname')- по классуgetElementById('id')|getElementsById('id')- по idgetElementByName('name')|getElementsByName('name')- по namegetElementByTagName('tag')|getElementsByTagName('tag')по тэгам
Первая колонка возвращает первый элемент, вторая колонка возвращает список потомков.
'*' - получить всех потомков.
getElementById(id): Браузер поддерживает у себя внутреннее соответствие id -> элемент, поэтому нужный элемент возвращается сразу.
querySelector(query): Перебирает потомков, проверяет их на удовлетворение query, останавливается на первом.
querySelectorAll(query): Перебирает всех потомков, проверяет их на удовлетворение query, собирает из в коллекцию.
Переборы происходят довольно быстро, поскольку осуществляется движком браузера, а не JS кодом.
Для оптимизации последние результаты кешируются. Кэш хранится до тех пор, пока документ не изменятся.
Результат запросов getElementsBy* – это не массив, а специальный объект, имеющий тип NodeList или HTMLCollection. Он похож на массив, так как имеет нумерованные элементы и длину, но внутри это не готовая коллекция, а «живой поисковой запрос». Поиск выполняется при обращении к коллекции или её длине.
An HTMLCollection in the HTML DOM is live; it is automatically updated when the underlying document is changed. — developer.mozilla.org
Для оптимизации firefox запоминает все найденные элементы, webkit запоминает только последний. При попытке обратиться к одному из предыдущих элементов поиска firefox просто вернёт его, а webkit будет искать его с начала или с текущего элемента в зависимости от того, что ближе.
Таким образом, метод firefox требует большей памяти, но имеет большую эффективность.При изменении документа кеш "умным образом" очищается (то есть очищается лишь то, что могло измениться в поиске).
elem.matches(css) - метод, проверяющий, удовлетворяет элемент css-селектору.
elem.closest(css) - метод, ищущий ближайший элемент выше по иерархии DOM (включая elem), удовлетворяющий css-селектору.
Разные узлы являются объектами разных классов, поэтому имеют разные свойства.
Примеры:<input> – HTMLInputElement, <body> – HTMLBodyElement, <a> – HTMLAnchorElement
Различия console.log() и console.dir(): первый выводит элемент в удобном виде для исследования HTML-структуры, второй - в виде JS-объекта для анализа его свойств.
Узнать класс можно:console.log(element.toString())console.dir(element)console.log(element.constructor)console.log(element.__proto__)
Проверить класс можно:console.log(element instanceof HTMLElement)
Cвойство nodeType содержит тип узла. Всего их 12. (element - 1, attribute - 2, text - 3, ... , comment - 8, ... ).
Свойство nodeName содержит название узла в uppercase.
Свойство tagName содержит тэг узла в uppercase. (только у element).
Свойство innerHTML хранит содержимое элемента в виде строки. Доступно для чтения и записи. Оно есть только у element. В него можно записать что угодно, но браузер исправит ошибки и вернёт валидный HTML-код. Оператор += для innerHTML не добавляет, а осуществляет перезапись всего содержимого, т.е. сперва всё удалится, а потом загрузится заново. Если в innerHTML есть скрипт с тэгом <script>, то он не выполнится.
Свойство outerHTML хранит содержимое элемента, включая сам элемент, в виде строки. Перезаписывать нельзя, потому что элемент не меняется, а заменяется на новый, который создаётся из нового outerHTML.
Свойство data хранит содержимое текстовых узлов, пробельных узлов и узлов-комментариев. Для чтения и записи. Для element имеет значение undefined.
Свойство textContent хранит весь текст внутри элемента (конкатенацию всех текстовых узлов). Текст внутри тэгов тоже берётся. Доступно для чтения и записи.
Свойство innerText возвращает текст в том виде, в котором он виден пользователю. (не стандартизировано)
Cвойство hidden отвечает за сокрытие элементов.
Существует много уникальных свойств: value для <input>, href для <a>.
Узнать свойства можно:console.dir(element)
Полифилл (polyfill) – это библиотека, которая добавляет в старые браузеры поддержку возможностей, которые в современных браузерах являются встроенными.
Проверка поддержки свойства или метода: их сравние с undefined.
Добавить методы можно:Element.prototype.method = function () {};
Добавить свойства можно:Object.defineProperty(Element.prototype, 'property', { set: function(value) {}, get: function() {} });
При чтении HTML браузер генерирует DOM-модель. При этом большинство стандартных HTML-атрибутов становятся свойствами соответствующих объектов (но не все, поэтому если хотим получить то же, что в HTML, то обращаемся через атрибут).
Атрибуты – то, что написано в HTML (в тэгах элементов).
DOM-свойства – то, что находится внутри DOM-объекта.
Значение атрибутов - всегда строка, значение свойств - любое.
Названия свойств регистрозависимы, название атрибутов - нет.
Свойства не видны в innerHTML, атрибуты - видны.
Методы работы с атрибутами:
elem.hasAttribute('name')– проверяет наличие атрибутаelem.getAttribute('name')– получает значение атрибутаelem.setAttribute('name', 'value')– устанавливает атрибутelem.removeAttribute('name')– удаляет атрибут
Свойство elem.attributes возвращает коллекцию (объектов типа Attr) всех атрибутов элемента.
Синхронизация между HTML-атрибутами и DOM-свойствами чаще всего односторонняя: свойство зависит от атрибута, но не наоборот.
Пример:
input.value = '...' не меняет атрибут input.getAttribute('value'), но input.setAttribute('value', '...') меняет input.value (то есть даже несмотря на то, что на странице изменения отобразятся, атрибут будет по-прежнему хранить исходное значение).
Можно придумывать свои свойства и добавлять их, так как DOM-узлы являются JS-объектами, но это никак не должно повлиять на отображение в HTML.
Можно приписывать несуществующие или несвойственные (нестандартные) атрибуты HTML-элементам, но это не повлечёт за собой появляение соответствующих свойств, хоть эти атрибуты и их значения можно будет получить через свойство attributes или метод getAttribute().
Можно использовать все виды атрибутов в css. Как стандартные: <input disabled> --> .input[disabled] {}, так и нестандартные: <button red> --> .button[red]{} .button[blue]{}, которые можно менять через elem.setAttribute().
Соответственно спецификации нестандартные атрибуты используются с припиской 'data-' в начале и называются data-атрибутами. В JS их можно получить с помощью свойства element.dataset: <div data-user-role> --> elem.dataset.userRole.
Атрибуту class соответствует свойство className, являющееся строкой из классов элемента, и свойство classList, являющееся коллекцией из классов элемента, имеющей свои методы.
Методы объекта classList:
elem.classList.contains('class')– возвращает true/false, в зависимости от того, есть ли у элемента класс class.elem.classList.add('class')– добавляет класс.elem.classList.remove('class')– удаляет класс.elem.classList.toggle('class')– если класса class нет, добавляет его, если есть – удаляет.
Методы для проверки позиции:
parent.contains(child)- проверяет вложенность.nodeA.compareDocumentPosition(nodeB)проверяет относительную друг от друга позицию элементов (возвращает битовую маску).
Методы создания и удаления:
document.createElement('tag')- создание элемента с указанным тэгом.document.createText('text')- создание текстового узла с указанным текстом.parentElem.appendChild(elem)- добавление элемента в конец списка детей родительского.parentElem.insertBefore(elem, next)- вставка элемента в родительский перед элементом next (next === null --> insertBefore ~ appendChild).elem.cloneNode(true)- глубокое копирование элемента (с атрибутами и подэлементами).elem.cloneNode(false)- копирование элемента (без подэлементов).parentElem.removeChild(elem)- удаление элемента из списка детей родительского.parentElem.replaceChild(newElem, elem)- замена элемента новым элементом.elem.remove()- удаление элемента напрямую (не требуя ссылки на родителя).
Вставка в document нужна для того, чтобы элемент отобразился. Методы вставки возвращают вставленный узел. Методы вставки автоматически удаляют элемент с предыдущего места, если он уже был вставлен в document. Методы удаления возвращают удалённый узел. При удалении элементов из childNodes, в нём всегда будет храниться текущее состояние (следующий элемент после удалённого встаёт на его место), поэтому обычный цикл for не сработает.
Метод insertAdjacentHTML вставляет произвольный HTML в любое место документа:
elem.insertAdjacentHTML('beforeBegin', html)- перед elem.elem.insertAdjacentHTML('afterBegin', html)- внутрь в начало elem.elem.insertAdjacentHTML('beforeEnd', html)- внутрь в конец elem.elem.insertAdjacentHTML('afterEnd', html)- после elem.
Методы insertAdjacentElement и insertAdjacentText работают аналогично.
Если использовать innerHTML += '...', которая не прибавляет, а заменяет всё содержимое, то все ресурсы будут перезагружены заново, все ссылки на предыдущие элементы станут не верны. К тому же этот способ позволяет вставлять только в конец.
DOM-объект DocumentFragment нужен в случае, если нужно вставить несколько DOM-узлов (какое-то поддерево) и при этом минимизировать количество операций с большим живым документом. Он похож на DOM-узел, но им не является, и у него нет обычных свойств DOM-узла. Посли вставки в DOM он исзечает, а на его место втавляются его дети.
Методы работы с DocumentFragment:
const fragment = document.createDocumentFragment()- создание фрагмента.fragment.appendChild(node)- наполнение фрагмента.fragment.cloneNode(true)- глубокое клонирование фрагментаelem.appendChild(fragment)- вставка фрагмента в DOM.
The DocumentFragment interface represents a minimal document object that has no parent. It is used as a lightweight version of Document that stores a segment of a document structure comprised of nodes just like a standard document. The key difference is that because the document fragment isn't part of the active document tree structure, changes made to the fragment don't affect the document. — developer.mozilla.org
Современные (эксперементальные) методы вставки/удаления:
node.append(...nodes)– вставляет nodes в конец node.node.prepend(...nodes)– вставляет nodes в начало node.node.after(...nodes)– вставляет nodes после узла node.node.before(...nodes)– вставляет nodes перед узлом node.node.replaceWith(...nodes)– вставляет nodes вместо node.
Методы document.write и document.writeln пишут текст напрямую в текст документа (в HTML), до того как браузер построит из него DOM (отсюда и оптимизация: нет необходимости модифицировать DOM). Поэтому HTML записывается как есть (не валидируется и не исправляется в отличие от innerHTML), а затем браузер учитывает написанное при построении DOM. Использование методов после загрузке приведёт к перезаписи данных на странице. Часто используются для добавления скриптов с динамическим url (реклама).
Свойство style – объект, в котором CSS-свойства пишутся в lowerCamelCase. Чтение и изменение его свойств – работа с компонентами атрибута style. Чтобы обнулить поставленный стиль, достаточно присвоить ему пустую строку (в этом случае оно берётся из css).
Указанные в style величины измерения переводятся в стандартные для браузера и далее хранятся в них. Cпецифичные свойства браузеров пишутся в CamelCase: 'Webkit...', 'Moz...'.
Свойство style.cssText позволяет задавать свойства в виде строки. При установке этого свойства все предыдущие свойства style очищаются.
Метод getComputedStyle позволяет получить текущее используемое значение свойства:
getComputedStyle(element)- получить стили элемента.getComputedStyle(element, 'pseudo')- получить стили с учётом псевдо-элемента (например, псевдокласс :hover).getComputedStyle(element).property- получить значение конкретного css-свойства элемента.getComputedStyle(element).getPropertyValue('property')- то же самое.
Метод getComputedStyle работает только для полных свойств (полное - marginTop, неполное - margin). Также метод не даёт информацию о псевдоклассе :visited (посещена ли ссылка), чтобы защитить информацию о пользователях.
Когда браузер рисует страницу, то он высчитывает дерево расположения элементов, иначе говоря «дерево геометрии» или «дерево рендеринга», которое содержит всю информацию о размерах.
Метрики у элементов:
offsetParent– «родитель по дереву рендеринга» – body для статического позиционирования или ближайший позиционированный элемент для других типов позиционирования.offsetLeft/offsetTop– позиция в пикселях левого верхнего угла блока, относительно его offsetParent.offsetWidth/offsetHeight– «внешняя» ширина/высота блока (полный размер, включая border).clientLeft/clientTop– отступ области содержимого от левого-верхнего угла элемента. Если ОС располагает вертикальную прокрутку справа, то равны ширинам левой/верхней рамки, если же слева, то clientLeft включает в себя прокрутку.clientWidth/clientHeight– ширина/высота содержимого. Включает в себя padding и не включает полосы прокрутки (видимая часть рассматриваемого элемента).scrollWidth/scrollHeight– ширина/высота содержимого, включая прокручиваемую область. Включает в себя padding и не включает полосы прокрутки.scrollLeft/scrollTop– ширина/высота прокрученной части документа, считается от верхнего левого угла.
Все эти свойства доступны только для чтения, кроме scrollLeft/scrollTop, изменение которых заставляет браузер прокручивать элемент.
Метрики для невидимых элементов равны нулю. Для элементов с display:none или находящихся вне документа дерево рендеринга не строится. Пустой div без ширины и высоты считается невидимым. Элементы за пределами экрана и элементы с visibility: 'hidden' считаются видимыми.
Лучше не использовать свойства width/height из getComputedStyle, поскольку они не во всех браузерах учитывают полосу прокрутки (альтернатива: clientWidth/clientHeight).


