Добавление интерактивности с помощью JavaScript

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

Краткое содержание

  • JavaScript может запрашивать и изменять DOM и CSSOM.
  • Блоки выполнения JavaScript в CSSOM.
  • JavaScript блокирует построение DOM, если оно явно не объявлено как асинхронное.

JavaScript — это динамический язык, который работает в браузере и позволяет нам изменять практически каждый аспект поведения страницы: мы можем изменять контент, добавляя и удаляя элементы из дерева DOM; мы можем изменить свойства CSSOM каждого элемента; мы можем обрабатывать ввод пользователя; и многое другое. Чтобы проиллюстрировать это, давайте дополним наш предыдущий пример «Hello World» простым встроенным скриптом:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

Попробуй

  • JavaScript позволяет нам проникнуть в DOM и получить ссылку на скрытый узел диапазона; узел может быть не виден в дереве рендеринга, но он все еще присутствует в DOM. Затем, когда у нас есть ссылка, мы можем изменить ее текст (через .textContent) и даже переопределить расчетное свойство стиля отображения с «none» на «inline». Теперь на нашей странице отображается « Привет, интерактивные студенты! ».

  • JavaScript также позволяет нам создавать, стилизовать, добавлять и удалять новые элементы в DOM. Технически вся наша страница может представлять собой один большой файл JavaScript, который создает и стилизует элементы один за другим. Хотя это и сработает, на практике использовать HTML и CSS гораздо проще. Во второй части нашей функции JavaScript мы создаем новый элемент div, устанавливаем его текстовое содержимое, стилизуем его и добавляем к телу.

предварительный просмотр страницы

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

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

Во-первых, обратите внимание, что в приведенном выше примере наш встроенный скрипт находится внизу страницы. Почему? Что ж, вы должны попробовать это сами, но если мы переместим скрипт выше элемента span , вы заметите, что скрипт дает сбой и жалуется, что не может найти ссылку ни на один элемент span в документе; то есть getElementsByTagName('span') возвращает значение null . Это демонстрирует важное свойство: наш скрипт выполняется именно в том месте, где он вставлен в документ. Когда анализатор HTML обнаруживает тег сценария, он приостанавливает процесс построения DOM и передает управление движку JavaScript; после завершения работы движка JavaScript браузер продолжает с того места, где остановился, и возобновляет построение DOM.

Другими словами, наш блок сценария не может найти какие-либо элементы на странице позже, потому что они еще не обработаны! Или, говоря немного иначе: выполнение нашего встроенного скрипта блокирует построение DOM, что также задерживает первоначальный рендеринг.

Еще одним тонким свойством внедрения скриптов на нашу страницу является то, что они могут читать и изменять не только свойства DOM, но и свойства CSSOM. Фактически, именно это мы и делаем в нашем примере, когда меняем свойство display элемента span с none на inline. Конечный результат? Теперь у нас есть состояние гонки.

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

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

  • Расположение сценария в документе имеет важное значение.
  • Когда браузер встречает тег скрипта, построение DOM приостанавливается до тех пор, пока скрипт не завершит выполнение.
  • JavaScript может запрашивать и изменять DOM и CSSOM.
  • Выполнение JavaScript приостанавливается до тех пор, пока CSSOM не будет готов.

В значительной степени «оптимизация критического пути рендеринга» относится к пониманию и оптимизации графа зависимостей между HTML, CSS и JavaScript.

Блокировка парсера и асинхронный JavaScript

По умолчанию выполнение JavaScript является «блокировкой синтаксического анализатора»: когда браузер встречает сценарий в документе, он должен приостановить построение DOM, передать управление среде выполнения JavaScript и позволить сценарию выполниться, прежде чем продолжить построение DOM. Мы видели это в действии со встроенным скриптом в нашем предыдущем примере. Фактически, встроенные скрипты всегда блокируют парсер, если вы не напишете дополнительный код для отсрочки их выполнения.

А как насчет сценариев, включенных через тег сценария? Давайте возьмем наш предыдущий пример и вынесем код в отдельный файл:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

приложение.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

Попробуй

Независимо от того, используем ли мы тег <script> или встроенный фрагмент JavaScript, вы ожидаете, что оба будут вести себя одинаково. В обоих случаях браузер приостанавливает и выполняет сценарий, прежде чем сможет обработать оставшуюся часть документа. Однако в случае внешнего файла JavaScript браузер должен сделать паузу, чтобы дождаться получения сценария с диска, из кэша или удаленного сервера, что может добавить десятки-тысячи миллисекунд задержки к критическому пути рендеринга.

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

Для этого мы помечаем наш скрипт как async :

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Попробуй

Добавление ключевого слова async в тег скрипта сообщает браузеру не блокировать построение DOM, пока он ожидает доступности скрипта, что может значительно повысить производительность.

Обратная связь