Лучшие практики манипулирования JS DOM – с примерами
В JavaScript вы можете манипулировать содержимым веб-страницы, используя объектную модель документа (DOM). Но как написать код, который будет читабельным, простым в обслуживании и не подверженным проблемам с производительностью? Это то, что мы рассмотрим в этой статье. Я расскажу о некоторых важных передовых практиках, которые помогут вам уверенно манипулировать DOM.
Пользователь события DOMContentLoaded
Событие DOMContentLoaded
вызывается при полной загрузке HTML-документа. Использование этого события гарантирует, что код манипуляции с DOM запустится только после полной загрузки документа.
Чтобы использовать DOMContentLoaded
, добавьте в документ прослушиватель событий и прослушивайте событие DOMContentLoaded
. Это помогает предотвратить любые проблемы, которые могут возникнуть при попытке манипулировать элементами, которые еще не отрисованы.
Пример:
document.addEventListener('DOMContentLoaded', function() {
// Your DOM manipulation code goes here...
})
Кэширование выбранных элементов
Если вы часто используете элементы, запрашивать DOM для одного и того же элемента снова и снова неэффективно. Лучше запросить DOM один раз и сохранить результат в переменных.
const cachedElement = document.getElementById('exampleId')
Таким образом, вы можете ссылаться на переменные в любое время, когда захотите их использовать. Это помогает повысить производительность, поскольку сокращает ненужную работу.
Запрос родительских элементов вместо документа
Когда вы кэшируете элемент, вы также можете запросить его, чтобы выбрать любого из его потомков. Это может помочь повысить производительность, поскольку ограничивает область запроса и уменьшает количество запросов ко всему документу.
Пример:
<div id="parent">
<p id="child">Example paragraph</p>
</div>
const parentElement = document.getElementById('parent')
// Options 1: Querying entire document ❌
const childFromDocument = document.getElementById('child')
// Options 2: Query the parent element ✅
const childFromParent = document.querySelector('#child')
В приведенном выше примере представлена простая разметка, содержащая элемент #parent
div и .child
paragraph. Тогда есть два варианта выбора дочернего элемента.
Технически оба варианта верны и выберут один и тот же элемент. Но разница заключается в объеме запроса.
Пример 1 запрашивает (или ищет) весь документ, чтобы найти и выбрать дочерний элемент. Это менее эффективно и даже необязательно, поскольку родительский элемент элемента, который вы собираетесь выбрать, уже кэширован.
Пример 2 сужает область запроса (или поиска), запрашивая только родительский элемент, а не весь документ. Вот почему он предпочтителен, потому что он более эффективен, особенно если документ большой.
Также обратите внимание, что для запроса родителя используется метод querySelector
. Использование getElementById
запроса к родителю не будет работать и приведет к ошибке.
Используйте классы CSS для стилизации элементов
Для стилизации элементов лучше использовать классы CSS, а не встроенные стили. Классы легко поддерживать по сравнению со встроенными стилями, которыми сложно управлять.
Свойство classList
имеет полезные свойства, такие как добавление, удаление, переключение и другие, которые позволяют легко изменять стили.
Пример:
.styledClass {
color: red;
}
element.classList.add('styledClass')
В этом примере используется .add
свойство classList
для добавления styledClass
к элементу. Предполагая, что вы хотите удалить класс из элемента, вы можете легко сделать это, используя свойство .remove
вместо add
.
Используйте innerHTMLс осторожностью
Свойство innerHTML
считывает и анализирует HTML-разметку, которую вы ему передаете. Это означает, что он может читать и запускать код в переданном ему теге сценария. И это может представлять угрозу безопасности вашего приложения.
По возможности используйте свойство innerText
или textContent
для визуализации строк. Но если вам нужно использовать innerHTML
, убедитесь, что вы используете его для вставки контента из надежных источников. Или очистите и проверьте предоставленный контент с помощью такой библиотеки, как DOMPurify.
Напишите читаемые прослушиватели событий
Часто вы передаете прослушивателям событий два аргумента. Первый — это событие, которое вы слушаете, а второй — обработчик события (функция, которая срабатывает при возникновении события).
Чтобы ваш код было легко читать и поддерживать, вы можете определить функцию обработчика событий вне прослушивателя событий. Затем вы можете вызвать его в прослушивателе четности, как в примере 1 ниже:
Example 1 ✅
MyElement.addEventListener('click', handleClick)
function handleClick() {
// your logic goes here..
}
// Example 2 ❌
myElement.addEventListener('click', function() {
// your logic goes here...
})
Оба технически правильны и будут делать одно и то же. Но пример 1 предпочтительнее, потому что его легче читать. Кроме того, при необходимости вы можете повторно использовать функцию handleClick
. Это поможет вам соблюдать принцип DRY (не повторяйте себя).
Используйте делегирование событий для обработки событий DOM
Делегирование событий — это когда вы прикрепляете прослушиватель событий к родительскому элементу для прослушивания событий его потомков. С помощью этого метода вы можете уменьшить количество прослушивателей событий, которые нужно включить в свой код.
Например, предположим, что у вас есть пять кнопок внутри родительского элемента div
:
<div id="parent">
<button id="btn-1">1st Button</button>
<button id="btn-2">2nd Button</button>
<button id="btn-3">3rd Button</button>
<button id="btn-4">4th Button</button>
<button id="btn-5">5th Button</button>
</div>
Вы можете добавить прослушиватель событий к каждой из пяти кнопок, чтобы прослушивать нажатия. Или, используя делегирование событий, вы можете использовать одно событие только в родительском div
:
const parentElement = document.getElementById('parent')
parentElement.addEventListener('click', handleClick)
function handleClick(event) {
alert(event.target.id)
}
В этом примере событие делегируется родительскому элементу. И мы используем event.target.id
, чтобы получить фактическую кнопку, которую нажал пользователь. Если вам интересно, вы можете запустить код на Stackblitz и посмотреть, как он работает.
Делегирование событий помогает сэкономить время и повысить производительность. Представьте себе, как этот метод может пригодиться при работе с большим количеством динамического контента.
Пакетное обновление DOM с помощью фрагмента
Частые обновления DOM могут повлиять на производительность вашего приложения. Постарайтесь сократить количество обновлений, где это возможно.
Полезной функцией, которую можно использовать для пакетного обновления, является свойство .createDocumentFragment
. Он позволяет группировать несколько обновлений перед их вставкой в документ. Это уменьшает количество перекомпоновок и делает ваш код более эффективным.
Пример без фрагмента:
const container = document.getElementById('container')
for (let i = 0; i < 1000; i++) {
const listItem = document.createElement('li')
listItem.textContent = `Item ${i}`
container.appendChild(listItem)
}
Этот код обновляется с каждой итерацией цикла. Это означает, что DOM будет обновляться 1000 раз. Существует более эффективный способ сделать это с помощью приведенного ниже кода, использующего фрагмент.
Пример с фрагментом:
const container = document.getElementById('container')
const fragment = document.createDocumentFragment()
// Add multiple list items to the fragment
for (let i = 0; i < 1000; i++) {
const listItem = document.createElement('li')
listItem.textContent = `Item ${i}`
fragment.appendChild(listItem)
}
container.appendChild(fragment)
Приведенный выше код добавляет listItem
к fragment
на каждой итерации цикла. Он добавляет дочерний элемент к container
элементу только после завершения цикла. Это означает, что DOM обновляется только один раз, а не 1000 раз, как раньше.
Используйте метод stopPropagation
Метод stopPropagation
управляет потоком событий в DOM. По умолчанию, когда событие происходит с элементом, оно всплывает (распространяется) через своих предков.
Такое поведение, связанное с распространением событий, иногда может привести к непредвиденным результатам. Этот stopPropagation
метод предоставляет способ остановить распространение события на родителя и других предков.
Давайте рассмотрим ситуацию, когда у вас есть кнопка внутри родительского элемента div
. И вы хотите обработать событие нажатия кнопки без регистрации щелчка по элементу div
:
<div id="container">
<button id="button">Click me</button>
</div>
const containerDiv = document.getElementById('container')
const buttonElement = document.getElementById('button')
containerDiv.addEventListener('click', handleDivClick)
buttonElement.addEventListener('click', handleBtnClick)
function handleDivClick() {
console.log('Div clicked')
}
function handleBtnClick(event) {
event.stopPropagation()
console.log('Button clicked')
}
Без использования этого метода stopPropagation
событие щелчка на кнопке также вызовет событие щелчка в родительском элементе div
. Это означает, что оба обработчика событий будут запущены.
Но event.stopPropagation()
строка в коде предотвратит запуск функции handleDivClick
, когда пользователь нажмет кнопку.
Вы можете запустить код на Stackblitz, чтобы увидеть, как он работает. Закомментируйте строку с методом stopPropagation
и увидите разницу.
Проверьте свой код управления DOM
Когда вы пишете тесты, вы создаете сценарии, имитирующие взаимодействие с пользователем или состояния приложения. Вы также убедитесь, что ваше приложение дает ожидаемые результаты.
Тестирование вашего кода манипуляции с DOM — лучшая практика, поскольку оно сделает ваш код надежным и простым в обслуживании. Это также дает вам уверенность в том, что ваш код ведет себя так, как ожидалось, даже если он развивается с течением времени, когда вы вносите изменения и добавляете функции.
Вы можете использовать платформы тестирования и библиотеки, доступные для JavaScript, такие как Jest, Mocha, Jasmine и другие, для автоматизации тестирования ваших приложений.
В следующем примере используется платформа Jest для проверки кода манипуляции DOM для добавления класса к элементу.
test('Adding a highlight class changes text color to red', () => {
myElement.classList.add('highlight');
expect(getComputedStyle(myElement).color).toBe('red');
});
Ожидается, что добавление класса highlight
изменит цвет текста на красный. Если тест пройден, это означает, что ваш код манипуляции с DOM работает должным образом. Если нет, вам нужно будет разобраться, что не так, и устранить проблему.
Заключение
В этой статье вы узнали десять рекомендаций, которые следует учитывать при работе с DOM. Некоторые из них носят общий характер, а другие зависят от конкретной ситуации. Используя эти рекомендации в своем рабочем процессе, вы будете создавать свои веб-приложения с базой кода, которую легко поддерживать.