Почему DOM работает медленно

внешний интерфейс

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

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

Как браузер отображает страницу

В браузере много модулей, один из которых отвечает за отрисовку страницыМодуль рендеринга, более знакомые — WebKit и Gecko и т. д., и здесь будет рассмотрено только содержимое этого модуля.
Кратко опишем процесс словами:

  • Разобрать HTML и сгенерировать DOM-дерево
  • Разбирайте различные стили и объединяйте их с деревом DOM для создания дерева рендеринга.
  • Рассчитать информацию о макете для каждого узла дерева рендеринга, например положение и размер блока.
  • Рисование в соответствии с деревом рендеринга и использованием слоя пользовательского интерфейса браузера.

вУзлы в дереве DOM и дереве рендеринга не находятся во взаимно однозначном соответствии., например, узел «display:none» будет существовать только в дереве DOM, а не в дереве рендеринга, потому что этот узел не нужно рисовать.
这里写图片描述
На приведенном выше рисунке показан основной процесс Webkit, который может отличаться от Gecko по терминологии.Вот блок-схема Gecko, но в содержании статьи ниже будет использоваться терминология Webkit единообразно.
这里写图片描述
На отрисовку страницы влияет множество факторов, например, положение ссылки повлияет на отрисовку первого экрана. Но здесь мы в основном фокусируемся на контенте, связанном с макетом.

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

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

При каких обстоятельствах браузер будет выполнять верстку

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

Вёрстка (reflow) обычно называется вёрсткой, эта операция используется для вычисления положения и размера элементов в документе и является важным этапом перед рендерингом. Когда HTML загружается в первый раз, будет макет, выполнение скрипта js и изменение стиля также заставят браузер выполнить макет, что также является основным содержанием этой статьи.

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

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

Следующие операции нарушат процедуру и заставят браузер выполнить макет.

  • Получить атрибуты DOM, которые необходимо вычислить через js
  • Добавить или удалить элементы DOM
  • изменить размер окна браузера
  • изменить шрифт
  • Активация псевдоклассов css, таких как: hover
  • Измените стиль элементов DOM с помощью js, и стиль включает изменение размера.

Давайте получим интуитивное ощущение на примере:

// Read
var h1 = element1.clientHeight;

// Write (invalidates layout)
element1.style.height = (h1 * 2) + 'px';

// Read (triggers layout)
var h2 = element2.clientHeight;

// Write (invalidates layout)
element2.style.height = (h2 * 2) + 'px';

// Read (triggers layout)
var h3 = element3.clientHeight;

// Write (invalidates layout)
element3.style.height = (h3 * 2) + 'px'; 1234567891011121314151617

clientHeight, это свойство необходимо вычислить, чтобы оно запускало макет браузера. Давайте взглянем на инструменты разработчика chrome (v47.0) (запись временной шкалы на скриншоте отфильтрована и отображается только макет):
这里写图片描述
В приведенном выше примере код сначала изменяет стиль элемента, а затем считывает атрибут clientHeight другого элемента. Из-за предыдущей модификации текущий DOM помечается как грязный. Чтобы гарантировать, что этот атрибут может быть точно получен , браузер будет иметь макет A (мы обнаружили, что инструменты разработчика Chrome добросовестно напомнили нам об этой проблеме с производительностью).

Оптимизировать этот код так же просто, как заранее прочитать необходимые свойства и изменить их вместе.

// Read
var h1 = element1.clientHeight;  
var h2 = element2.clientHeight;  
var h3 = element3.clientHeight;

// Write (invalidates layout)
element1.style.height = (h1 * 2) + 'px';  
element2.style.height = (h2 * 2) + 'px';  
element3.style.height = (h3 * 2) + 'px'; 123456789

Взгляните на это время:
这里写图片描述
Некоторые другие схемы оптимизации представлены ниже.

Решение для минимизации макета

Упомянутое выше пакетное чтение и запись - это одно, в основном из-за получения значения атрибута, которое необходимо вычислить, так какие же значения необходимо вычислить?

Эта ссылка описывает большинство свойств, которые необходимо рассчитать:Поговори с ним.IL core.com/2011/03/how…

Рассмотрим другие ситуации:

Столкнувшись с серией манипуляций с DOM

Для ряда операций DOM (добавление, удаление и изменение элементов DOM) доступны следующие решения:
- documentFragment
- display: none
- cloneNode
Например (просто возьмите documentFragment в качестве примера):

var fragment = document.createDocumentFragment();  
for (var i=0; i < items.length; i++){  
  var item = document.createElement("li");
  item.appendChild(document.createTextNode("Option " + i);
  fragment.appendChild(item);
}
list.appendChild(fragment); 1234567

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

изменение стиля лица

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

Вот триггеры CSS веб-сайта, подробно описывающие влияние различных свойств CSS на выполнение браузером макета и рисования.

В приведенном ниже случае это то же самое, что и упомянутая выше часть оптимизации, просто обратите внимание на чтение и запись.

elem.style.height = "100px"; // mark invalidated  
elem.style.width = "100px";  
elem.style.marginRight = "10px";

elem.clientHeight // force layout here 12345

Но если упомянуть анимацию, вот анимация js, например:

function animate (from, to) {  
  if (from === to) return

  requestAnimationFrame(function () {
    from += 5
    element1.style.height = from + "px"
    animate(from, to)
  })
}

animate(100, 500) 1234567891011

Each frame of the animation will lead layout, which can not be avoided, but in order to reduce the loss of performance caused by layout animation, animation can be absolutely positioned elements, so animated elements from the text flow, computational layout will be reduced a много.

использовать запросAnimationFrame

Любая операция, которая может вызвать перерисовку, должна быть помещена в requestAnimationFrame.

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

// Read
var h1 = element1.clientHeight;

// Write
requestAnimationFrame(function() {  
  element1.style.height = (h1 * 2) + 'px';
});

// Read
var h2 = element2.clientHeight;

// Write
requestAnimationFrame(function() {  
  element2.style.height = (h2 * 2) + 'px';
});123456789101112131415

这里写图片描述
Вы можете четко наблюдать время срабатывания кадра анимации, MDN говорит, что он срабатывает до рисования, но я предполагаю, что он выполняется до того, как скрипт js передает управление браузеру для недействительной проверки DOM.

Другие примечания

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

Кэшировать результат селектора, уменьшить запрос DOM. Здесь мы должны упомянуть следующую HTMLCollection. Тип объекта HTMLCollection получается с помощью document.getElementByTagName, и типы массивов очень похожи, но каждый раз, когда объект полученного свойства эквивалентен выполнению запроса DOM:

var divs = document.getElementsByTagName("div");  
for (var i = 0; i < divs.length; i++){  //infinite loop  
  document.body.appendChild(document.createElement("div"));
}1234

Например, приведенный выше код вызовет бесконечный цикл, поэтому выполняйте кэширование при работе с объектами HTMLCollection.

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

При запросе к DOM в последнюю очередь следует выбирать querySelector и querySelectorAll. Они самые мощные, но эффективность выполнения очень низкая. Если возможно, попробуйте заменить их другими методами.

Следующие две ссылки jsperf могут сравнить производительность.

1)Элементы JSP и F.com/get…

2)JSP, а элемент F.com/get...

Мои собственные мысли о слое View

Вышеупомянутое содержание является более теоретическим, с практической точки зрения, обсуждаемое выше содержимое — это именно то, с чем должен иметь дело слой представления. Для этого уже есть библиотека FastDOM, но ее код таков:

fastdom.read(function() {  
  console.log('read');
});

fastdom.write(function() {  
  console.log('write');
});1234567

Проблема очевидна, это приведет к callback hell, а также предвидится, что коду Imperative вроде FastDOM не хватает масштабируемости, ключ в том, что после использования requestAnimationFrame это становится проблемой асинхронного программирования. Для синхронизации статуса чтения и записи необходимо написать Wrapper на основе DOM для внутреннего управления асинхронным чтением и записью.

Короче говоря, постарайтесь избежать проблем, упомянутых выше, но если вы используете библиотеку, такую ​​как jQuery, проблема макета заключается в абстракции самой библиотеки. Например, React вводит свою собственную компонентную модель, использует виртуальный DOM для сокращения операций DOM и может иметь только один макет при каждом изменении состояния, я не знаю, используется ли внутренне requestAnimationFrame, я чувствую, что необходимо сделать хороший вид Слой Это довольно сложно, а затем приготовьтесь изучать код React. Я надеюсь, что когда я вернусь к этому вопросу через год или два, у меня появятся новые идеи.