Схема оптимизации DOM

внешний интерфейс JavaScript
Схема оптимизации DOM

1. Перерисовать и переставить

1.1 Что такое перерисовка и перекомпоновка

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

1.2 Страница рендеринга в браузере

Когда вы идете на собеседование, вам всегда будут задавать вопрос, то есть «Что происходит, когда вы вводите строку url в браузере?» Ответ на этот вопрос должен отвечать не только знанию сети, но и также включает в себя отображение страницы браузером. Когда наш браузер получает ответ от сервера, он начинает рендеринг построчно. Когда он сталкивается с CSS, он асинхронно вычисляет значение атрибута, а затем продолжает синтаксический анализ DOM. После завершения синтаксического анализа дерево DOM будет формируется, который будет асинхронно вычисляться. Хороший стиль (блок стиля) объединяется с деревом DOM, чтобы стать деревом рендеринга, которое затем рисуется на странице браузером. Разница между деревом DOM и деревом рендеринга заключается в том, что узлы со стилем display:none; будут в дереве DOM, но не в дереве рендеринга. После того, как браузер отрисовывает, он начинает анализировать файл js и определяет, нужно ли перерисовывать и переупорядочивать в соответствии с js.

1.3 Причины перерисовки и перестановки

Факторы, вызывающие перекрашивание:

  • Изменение атрибутов стиля, таких как видимость, контур, цвет фона и т. д., не меняет размер, положение и т. д. элемента. Браузер перерисует на основе новых свойств элемента.

Факторы, вызывающие перестройку:

  • Изменения содержания
  • Изменения текста или изменения размера изображения
  • Изменения геометрических свойств элементов DOM
    • Например, когда ширина и высота элемента DOM изменены, соответствующие узлы в исходном дереве рендеринга станут недействительными, и браузер переупорядочит соответствующие узлы в дереве рендеринга в соответствии с измененным DOM. Если геометрические свойства родительского узла изменяются, это также заставит его дочерние узлы и последующие одноуровневые узлы пересчитать положение и т. д., что приведет к серии перестановок.
  • Изменения структуры дерева DOM
    • Добавление узлов DOM, изменение положения узлов DOM и удаление узла — все это изменения в дереве DOM, которые вызывают переупорядочение страниц. Макет браузера — это процесс сверху вниз, изменение текущего элемента не повлияет на элементы, которые были пройдены перед ним, но если новый элемент будет добавлен перед всеми узлами, все последующие элементы будут переставлены.
  • получить некоторые свойства
    • Помимо прямого изменения дерева рендеринга, браузер также перестраивается для получения правильного значения при получении значений некоторых свойств, к которым относятся: offsetTop, offsetLeft, offsetWidth, offsetHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, clientTop , clientLeft , clientWidth, clientHeight, getComputedStyle().
  • Изменение размера окна браузера
    • Изменение размера окна повлияет на изменение размера элементов во всей веб-странице, то есть на изменение установленного атрибута DOM-элемента, поэтому вызовет перестановку.
  • Внешний вид полос прокрутки (которые вызывают перекомпоновку всей страницы)

Короче говоря, вам нужно знать, что js является однопоточным.Перерисовка и переупорядочивание блокируют пользовательские операции и влияют на производительность веб-страниц.Когда страница перерисовывается и переупорядочивается несколько раз, например, напишите таймер для изменения элементов страницы каждый 500ms ширина и высота, то эта страница может все больше и больше застревать, мы должны максимально уменьшить перерисовку и перекомпоновку. Тогда наша оптимизация DOM также основана на этом начале.

2. Оптимизация

2.1 Ограничение доступа

Сокращение количества посещений естественно означает кеширование элементов, но обратите внимание

var ele = document.getElementById('ele');

Это не кеширование ele.Каждый вызов ele эквивалентен посещению узла с идентификатором ele.

2.1.1 Список узлов кэша

var foods = document.getElementsByClassName('food');

Мы можем использовать food[i] для доступа к i-му элементу с классом food, но food здесь не массив, а NodeList. NodeList похож на массив, который содержит несколько упорядоченных узлов и может получить доступ к этим узлам по положению. Объект NodeList является динамическим, и для каждого посещения выполняется запрос на основе документа. Итак, мы хотим свести к минимуму количество посещений NodeList, мы можем рассмотреть возможность кэширования значения NodeList.

// 优化前
var lis = document.getElementsByTagName('li');

for(var i = 0; i < lis.length; i++) {
     // do something...  
}

// 优化后,将length的值缓存起来就不会每次都去查询length的值
var lis = document.getElementsByTagName('li');

for(var i = 0, len = lis.length; i < len; i++) {
     // do something...  
}

И поскольку odelist динамичен, если он не кэшируется, он может вызвать бесконечную петлю, такую ​​как добавление элементов, получая длину odelist.

2.1.2 Смена селектора

Есть два наиболее распространенных способа получения элементов — getElementsByXXX() и queryselectorAll(), разница между этими двумя селекторами очень большая: первый — для получения динамических коллекций, а второй — для получения, например, статических коллекций.

// 假设一开始有2个li
var lis = document.getElementsByTagName('li');  // 动态集合
var ul = document.getElementsByTagName('ul')[0];
 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);
    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}
// 输出结果:2, 3, 4
// 优化后
var lis = document.querySelectorAll('li');  // 静态集合 
var ul = document.getElementsByTagName('ul')[0];
 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);
    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}
// 输出结果:2, 2, 2

Операции со статическими коллекциями не вызывают повторных запросов документов и более оптимизированы, чем динамические коллекции.

2.1.3 Избегайте ненужных циклов

// 优化前
for(var i = 0; i < 10; i++) {
    document.getElementById('ele').innerHTML += 'a'; 
} 
// 优化后 
var str = ''; 
for(var i = 0; i < 10; i++) {
    str += 'a'; 
}
document.getElementById('ele').innerHTML = str;

Код до оптимизации обращается к элементу ele 10 раз, тогда как код после оптимизации обращается к нему только один раз, что значительно повышает эффективность.

2.1.4 Делегирование мероприятия

Функции мероприятия в JS являются все объекты. Если есть слишком много функций событий, он займет много памяти, и тем больше DOM элементов, связанные с событием, тем более раз доступа к доме, и интерактивное время доступа к Страница также будет отложена. Следовательно, родилась делегация событий. Делегация событий использует пузырька событий, и можно указать только один обработчик событий для управления всеми событиями определенного типа.

// 事件委托前
var lis = document.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++) {
   lis[i].onclick = function() {
      console.log(this.innerHTML);
   };  
}    

// 事件委托后
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function(event) {
   console.log(event.target.innerHTML);
};

Мы посетили перед делегатом события lis.length раз li, но после использования делегатов события мы посетили только один раз ul.

2.2 Уменьшить перерисовку и перестановку

2.2.1 Изменение нескольких стилей узла dom

Мы хотим изменить ширину и высоту элемента div, обычно это может быть так

var div = document.getElementById('div1');
div.style.width = '220px';
div.style.height = '300px';

Описанная выше операция изменяет два атрибута элемента, трижды посещает DOM, запускает два перекомпоновки и две перерисовки. Мы сказали, что оптимизация заключается в уменьшении количества посещений и уменьшении количества перерисовок и повторных заказов.От этой отправной точки можем ли мы посетить элемент только один раз и уменьшить количество повторных заказов до 1? Очевидно да, мы можем написать класс в css

/* css
.change {
    width: 220px;
    height: 300px;
}
*/
document.getElementById('div').className = 'change';

Таким образом, вы можете работать с несколькими стилями одновременно.

2.2.2 Пакетное изменение стиля узла dom

Случай с приведенным выше кодом относится к узлу dom. Что, если мы хотим изменить стиль коллекции dom? Первый метод, который приходит на ум, — пройтись по коллекции и добавить className к каждому узлу. Подумайте еще раз, не посещает ли он узел dom несколько раз? Подумайте о разнице между деревом dom и деревом рендеринга, упомянутым в начале статьи.Если атрибут отображения узла равен none, то этот узел не будет существовать в дереве рендеринга, а значит, работа этого узла будет не повлияет на дерево рендеринга и, следовательно, не вызовет перерисовку и перестановку, исходя из этой идеи, мы можем добиться оптимизации:

  • Показать родительский элемент коллекции, который нужно изменить display: none;
  • Затем перейдите и измените узлы коллекции
  • Установит родительский элемент display: block;
// 假设增加的class为.change
var lis = document.getElementsByTagName('li');  
var ul = document.getElementsByTagName('ul')[0];

ul.style.display = 'none';

for(var i = 0; i < lis.length; i++) {
    lis[i].className = 'change';  
}

ul.style.display = 'block';

2.2.3 DocumentFragment

Сторона createDocumentFragment() используется для создания объекта виртуального узла, или, другими словами, используется для создания узла фрагмента документа. Он может содержать различные типы узлов и изначально пуст.

Узлы DocumentFragment не принадлежат дереву документа, а унаследованное свойство parentNode всегда имеет значение null. Он имеет очень полезную функцию при запросе вставки узла DocumentFragment в дерево документа,Вставляется не сам DocumentFragment, а все его дочерние узлы.Эта функция делает DocumentFragment заполнителем, временно сохраняя узлы, которые вставляются в документ одновременно.

Кроме того, когда необходимо добавить несколько элементов DOM, если эти элементы сначала добавляются в DocumentFragment, а затем DocumentFragment равномерно добавляется на страницу, количество раз, которое страница отображает DOM, будет уменьшено, а эффективность будет снижена. быть значительно улучшена.

Инструкции:

var frag = document.createDocumentFragment(); //创建一个DOM片段		
for(var i=0;i<10000;i++) {				
    var li = document.createElement("li");			
    li.innerHTML = i;		
    frag.appendChild(li);  //将li元素加到文档碎片上	
} 			
ul.appendChild(frag);  //将文档碎片加到ul上

3. Резюме

  • Сократить количество посещений дома
    • Значения атрибутов узла кэширования
    • Использование селекторов
    • Избегайте ненужных циклов
    • делегация мероприятия
  • Уменьшите количество перерисовок и перекомпоновок
    • Используйте className для изменения нескольких стилей
    • Исключить родительский элемент из потока документов и возобновить его
    • DocumentFragment

Если я увижу другие решения по оптимизации в будущем, я обновлю их, и вы можете связаться со мной.

Справочная документация: