Механизм рендеринга страницы в браузере, о котором вы не знаете

JavaScript

предисловие

Ядро браузера относится к основной программе, которая поддерживает работу браузера и делится на две части: одна — это механизм рендеринга, а другая — это JS-движок. Механизм рендеринга не одинаков в разных браузерах. В настоящее время распространенные на рынке ядра браузеров можно разделить на следующие четыре типа: Trident (IE), Gecko (Firefox), Blink (Chrome, Opera) и Webkit (Safari). Возможно, наиболее знакомым всем здесь является ядро ​​Webkit, которое является настоящим повелителем в современном мире браузеров. В этой статье мы возьмем Webkit в качестве примера, чтобы провести глубокий анализ процесса рендеринга современных браузеров.

Чтобы прочитать больше качественных статей, нажмитеБлог GitHub, пятьдесят качественных статей в год ждут вас!

процесс загрузки страницы

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

Основные моменты заключаются в следующем:

  • Браузер получает IP-адрес доменного имени от DNS-сервера.
  • Отправьте HTTP-запрос на машину с этим IP-адресом
  • Сервер получает, обрабатывает и возвращает HTTP-запрос
  • Браузер получает возвращаемый контент

Например, введите в браузереhttps://juejin.im/timeline, а затем через разрешение DNS,juejin.imСоответствующий IP-адрес36.248.217.149(IP-адрес, соответствующий другому времени и месту, может быть другим). Затем браузер отправляет HTTP-запрос на этот IP-адрес.

Сервер получает HTTP-запрос, затем выполняет вычисления (отправляет разный контент разным пользователям) и возвращает HTTP-запрос. Возвращенный контент выглядит следующим образом:

По сути, это набор строк в формате HMTL, потому что только браузеры формата HTML могут правильно их разобрать, что является требованием стандарта W3C. Следующим шагом является процесс рендеринга браузера.

процесс рендеринга в браузере

Процесс рендеринга в браузере можно условно разделить на следующие три части:

1) Браузер анализирует три вещи:

  • Одним из них является HTML/SVG/XHTML Строка HTML описывает структуру страницы, и браузер анализирует строку структуры HTML в древовидную структуру DOM.

  • Второй — CSS.Синтаксический анализ CSS создаст дерево правил CSS, которое похоже на структуру DOM.

  • Третий — сценарий Javascript.После загрузки файла сценария Javascript дерево DOM и дерево правил CSS управляются через API DOM и API CSSOM.

2) После завершения синтаксического анализа движок браузера построит дерево рендеринга с помощью дерева DOM и дерева правил CSS.

  • Дерево рендеринга Дерево рендеринга отличается от дерева DOM Дерево рендеринга включает только узлы, которые необходимо отобразить, и информацию о стиле этих узлов.
  • Дерево правил CSS в основном предназначено для завершения сопоставления и прикрепления правила CSS к каждому элементу (то есть к каждому кадру) в дереве визуализации.
  • Затем вычисляется положение каждого кадра, что также называется процессом компоновки и перекомпоновки.

3) Наконец, нарисуйте, вызвав API родного графического интерфейса операционной системы.

Далее мы подробнее остановимся на важных шагах, которые мы прошли.

Создайте DOM

Браузеры выполняют ряд шагов для преобразования файла HTML в дерево DOM. Макроскопически его можно разделить на несколько этапов:

构建DOM的具体步骤

  • Браузер считывает необработанные байты HTML с диска или из сети и преобразует их в строки в соответствии с указанной кодировкой файла (например, UTF-8).

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

  • Преобразовать строку в токен, например:<html>,<body>Ждать.Токен будет определять, является ли текущий токен «начальным тегом», «конечным тегом» или «текстом», а также другую информацию..

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

Фактически, это роль токена для идентификации «начального тега» и «конечного тега». Например, узел между начальным и конечным тегами токена «title» должен быть дочерним узлом «head».

На приведенном выше рисунке показана взаимосвязь между узлами, например: токен «Hello» расположен между начальным тегом «title» и конечным тегом «title», указывая, что токен «Hello» является дочерним узлом токена «title». Точно так же токен «title» является дочерним узлом токена «head».

  • Создание объекта узла и создает домо

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

Далее возьмем пример, предположив, что есть фрагмент HTML-текста:

<html>
<head>
    <title>Web page parsing</title>
</head>
<body>
    <div>
        <h1>Web page parsing</h1>
        <p>This is an example Web page.</p>
    </div>
</body>
</html>

Приведенный выше HTML будет проанализирован следующим образом:

Построить CSSOM

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

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

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

Примечание. Сопоставление HTML-элементов с помощью CSS — довольно сложный вопрос производительности. Поэтому дерево DOM должно быть небольшим, CSS должен максимально использовать идентификатор и класс и не каскадироваться вниз..

Построить дерево рендеринга

После создания дерева DOM и дерева CSSOM нам нужно объединить два дерева в дерево рендеринга.

В этом процессе речь идет не просто о слиянии двух.Дерево рендеринга будет включать только те узлы, которые необходимо отобразить, и информацию о стиле для этих узлов., если узелdisplay: none, то он не будет отображаться в дереве рендеринга.

У нас могут возникнуть сомнения:Что делать браузеру, если он встречает JS-файл во время рендеринга?

Во время рендеринга, если вы столкнулись<script>Просто остановите рендеринг и выполните код JS. Поскольку в браузере есть поток рендеринга GUI и поток движка JS, чтобы предотвратить непредсказуемые результаты рендеринга, эти два потока являются взаимоисключающими. Загрузка, синтаксический анализ и выполнение JavaScript блокирует построение DOM, то есть, когда анализатор HTML встречает JavaScript во время построения DOM, он приостанавливает построение DOM, передает управление движку JavaScript, и подождите, пока движок JavaScript завершит работу. , браузер возобновит построение DOM с того места, где он остановился.

То есть, если вы хотите, чтобы первый экран отображался быстрее, JS-файл не должен загружаться на первом экране, поэтому рекомендуется размещать тег script в нижней части тега body. Конечно, на данный момент это не означает, что теги скрипта должны быть внизу, потому что вы можете добавить атрибуты отсрочки или асинхронности к тегам скрипта (разница между ними будет объяснена ниже).

Файлы JS не просто блокируют построение DOM, они также заставляют CSSOM блокировать построение DOM..

Первоначально построение DOM и CSSOM не влияли друг на друга, и колодезная вода не делала речную воду, однако после появления JavaScript CSSOM также стал блокировать построение DOM. Только после построения CSSOM DOM возобновил работу с DOM. строительство.

Что тут происходит?

Это связано с тем, что JavaScript может не только изменять DOM, но и изменять стили, что означает, что он может изменять CSSOM. Поскольку неполный CSSOM нельзя использовать, если JavaScript хочет получить доступ к CSSOM и изменить его, он должен иметь возможность получить полный CSSOM при выполнении JavaScript. Так что это приводит к явлению, если браузер не завершил загрузку и построение CSSOM, а мы хотим запустить скрипт в это время, то браузер отложит выполнение скрипта и построение DOM до тех пор, пока не завершит загрузку и построение CSSOM . То есть,В этом случае браузер загружает и строит CSSOM, затем выполняет JavaScript, а затем продолжает строить DOM..

макет и рисунок

После того, как браузер сгенерирует дерево рендеринга, оно будет размещено в соответствии с деревом рендеринга (также называемым перекомпоновкой). На этом этапе браузер должен определить точное местоположение и размер каждого узла на странице. Часто такое поведение также называют «автоперекомпоновкой».

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

Сразу после завершения компоновки браузер генерирует события «Paint Setup» и «Paint» для преобразования дерева рендеринга в пиксели на экране.

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

Несколько дополнительных замечаний

1. Какова роль асинхронности и отсрочки? Какая разница?

Далее давайте сравним разницу между свойствами defer и async:

async和defer

Синяя линия представляет загрузку JavaScript, красная линия — выполнение JavaScript, зеленая линия — синтаксический анализ HTML.

1) Случай 1<script src="script.js"></script>

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

2) Случай 2<script async src="script.js"></script> (Асинхронная загрузка)

Атрибут async означает асинхронное выполнение входящего JavaScript, отличие от defer в том, что если он был загружен, он начнет выполняться — будь то на этапе парсинга HTML или после срабатывания DOMContentLoaded. Следует отметить, что JavaScript, загруженный таким образом, все равно будет блокировать событие загрузки. Другими словами, асинхронный скрипт может выполняться до или после срабатывания DOMContentLoaded, но он должен выполняться до срабатывания загрузки.

3) Случай 3<script defer src="script.js"></script>(отложенное исполнение)

Атрибут defer указывает на отложенное выполнение введенного JavaScript, то есть HTML-код не прекращает синтаксический анализ при загрузке JavaScript, и два процесса идут параллельно. После анализа всего документа и загрузки отложенного сценария (порядок этих двух вещей не имеет значения) выполняется весь код JavaScript, загруженный отложенным сценарием, а затем запускается событие DOMContentLoaded.

По сравнению с обычным скриптом, defer имеет два отличия:Синтаксический анализ HTML не блокируется при загрузке файла JavaScript, а фаза выполнения размещается после завершения синтаксического анализа тега HTML. При загрузке нескольких JS-скриптов асинхронная загрузка выполняется непоследовательно, а отложенная — последовательной.

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

Думайте о DOM и JavaScript как об островах, каждый из которых соединен платными мостами. - "Высокопроизводительный JavaScript"

JS работает быстро, и изменение объектов DOM в JS также происходит быстро. В мире JS все просто и быстро. Но манипулирование DOM — это не сольный танец JS, а совместная работа двух модулей.

Потому что DOM — это то, что принадлежит движку рендеринга, а JS — это то, что принадлежит движку JS. Когда мы используем JS для управления DOM, это, по сути, «трансграничная связь» между механизмом JS и механизмом рендеринга. Реализация этой «трансграничной связи» не проста, она опирается на интерфейс моста как на «мост» (как показано на рисунке ниже).

За проезд по «мосту» взимается плата — сама стоимость немаленькая. Каждый раз, когда мы манипулируем DOM (будь то для модификации или просто для доступа к его значениям), мы проходим через «мост». Через «мост» больше раз это приведет к более очевидным проблемам с производительностью. Так что предложение «уменьшить манипуляции с DOM» не лишено оснований.

3. Вы действительно разбираетесь в рефлоу и перерисовке?

Процесс рендеринга в основном такой (четыре шага, выделенные желтым цветом на рисунке ниже): 1. Рассчитать стиль CSS 2. Построить дерево рендеринга 3. Макет — координаты позиционирования и размер 4. Официально начать рисование

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

Здесь важно упомянуть две концепции: одна — Reflow, а другая — Repaint.

  • Перерисовка: когда наша модификация DOM приводит к изменению стиля, не влияя на его геометрические свойства (например, изменение цвета или цвета фона), браузеру не нужно пересчитывать геометрические свойства элемента, и он напрямую рисует новые элементы. для элемента element.style (пропускает ссылку на перекомпоновку, показанную выше).
  • Перекомпоновка: когда наша модификация DOM вызывает изменение геометрического размера DOM (например, изменение ширины, высоты элемента или скрытие элемента и т. д.), браузеру необходимо пересчитать геометрические свойства элемента ( геометрические свойства и положения других элементов также будут соответствующим образом затронуты), а затем нанесите на график расчетные результаты. Этот процесс называется перекомпоновкой (также называемой перестановкой).

Мы знаем, что при создании веб-страницы она будет отображаться хотя бы один раз. В процессе доступа пользователя он будет продолжать перерисовываться. Повторный рендеринг повторит reflow + redraw или просто перерисует.Перерисовка должна происходить при перерисовке, а перерисовка не может вызывать перерисовку. Перерисовки и перекомпоновки часто происходят, когда мы стилизуем узлы, и также могут в значительной степени повлиять на производительность. Стоимость перекомпоновки намного выше, чем стоимость перерисовки, и изменение дочерних узлов в родительском узле, скорее всего, вызовет серию перекомпоновок родительского узла.

1) Общие свойства и методы, вызывающие перекомпоновку

Любая операция, изменяющая геометрию элемента (положение и размер элемента), вызовет перекомпоновку,

  • Добавляйте или удаляйте видимые элементы DOM;
  • Изменения размера элемента - поля, отступы, границы, ширина и высота
  • Изменения содержимого, например ввод пользователем текста в поле ввода.
  • Изменение размера окна браузера - когда происходит событие изменения размера
  • Вычислить свойства offsetWidth и offsetHeight
  • Установите значение свойства стиля

2) Общие свойства и методы, вызывающие перерисовку

3) Как уменьшить перекомпоновку и перерисовку

  • Используйте преобразование вместо верха
  • Используйте видимость вместо display: none , потому что первая вызывает только перерисовку, а вторая вызывает перекомпоновку (меняет макет).
  • Не помещайте значение свойства узла в цикл как переменную в цикле.
for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}
  • Не используйте макет таблицы, небольшое изменение может привести к перестановке всей таблицы.
  • Выбор скорости реализации анимации, чем быстрее скорость анимации, тем больше время перекомпоновки, также можно выбрать использование requestAnimationFrame
  • Селекторы CSS сопоставляются и ищутся справа налево, чтобы избежать слишком большого количества уровней узлов.
  • Установите узел, который часто перерисовывается или перекомпоновывается как слой, слой может предотвратить влияние поведения рендеринга этого узла на другие узлы. Например, для тега видео браузер автоматически превратит узел в слой.

стратегия оптимизации производительности

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

  • JS-оптимизация:<script>Тег плюс атрибут defer и асинхронный атрибут используются для управления загрузкой и выполнением скрипта, не блокируя синтаксический анализ документа страницы.
    • Атрибут defer: используется для запуска нового потока для загрузки файла сценария и запуска сценария после завершения синтаксического анализа документа.
    • асинхронный атрибут: новый атрибут HTML5, используемый для асинхронной загрузки файлов сценариев и немедленной интерпретации и выполнения кода после загрузки.
  • CSS-оптимизация:<link>Значение атрибута в атрибуте rel тега установлено на preload, что позволяет указать в вашей HTML-странице, какие ресурсы необходимы сразу после загрузки страницы, оптимально настроить порядок загрузки и улучшить производительность рендеринга.

Суммировать

Таким образом, мы опираемся следующие выводы:

  • Рабочий процесс в браузере: Построить DOM -> Построить CSSOM -> Построить дерево рендеринга -> Макет -> Рисовать.
  • CSSOM заблокирует рендеринг, и только когда CSSOM будет построен, он перейдет к следующему этапу построения дерева рендеринга.
  • Обычно DOM и CSSOM строятся параллельно, но когда браузер встречает тег скрипта без атрибутов defer или async, построение DOM приостанавливается, если браузер не закончил загрузку и построение CSSOM, потому что JavaScript может модифицировать CSSOM. , поэтому вам нужно дождаться сборки CSSOM перед выполнением JS и, наконец, перестроить DOM.

Добро пожаловать в публичный аккаунт:Мастер по фронтенду, мы будем свидетелями вашего роста вместе!

Справочная статья