Перейти к содержимому

Зачем разделяют html дерево на компоненты

  • автор:

DOM-дерево

В соответствии с объектной моделью документа («Document Object Model», коротко DOM), каждый HTML-тег является объектом. Вложенные теги являются «детьми» родительского элемента. Текст, который находится внутри тега, также является объектом.

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

Например, document.body – объект для тега .

Если запустить этот код, то станет красным на 3 секунды:

document.body.style.background = 'red'; // сделать фон красным setTimeout(() => document.body.style.background = '', 3000); // вернуть назад

Это был лишь небольшой пример того, что может DOM. Скоро мы изучим много способов работать с DOM, но сначала нужно познакомиться с его структурой.

Пример DOM

Начнём с такого, простого, документа:

   О лосях  Правда о лосях. 

DOM – это представление HTML-документа в виде дерева тегов. Вот как оно выглядит:

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

Каждый узел этого дерева – это объект.

Теги являются узлами-элементами (или просто элементами). Они образуют структуру дерева: – это корневой узел, и его дочерние узлы и т.д.

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

Например, в теге есть текстовый узел «О лосях» .

Обратите внимание на специальные символы в текстовых узлах:

  • перевод строки: ↵ (в JavaScript он обозначается как \n )
  • пробел: ␣

Пробелы и переводы строки – это полноправные символы, как буквы и цифры. Они образуют текстовые узлы и становятся частью дерева DOM. Так, в примере выше в теге есть несколько пробелов перед , которые образуют текстовый узел #text (он содержит в себе только перенос строки и несколько пробелов).

Существует всего два исключения из этого правила:

  1. По историческим причинам пробелы и перевод строки перед тегом игнорируются
  2. Если мы записываем что-либо после закрывающего тега , браузер автоматически перемещает эту запись в конец body , поскольку спецификация HTML требует, чтобы всё содержимое было внутри . Поэтому после закрывающего тега не может быть никаких пробелов.

В остальных случаях всё просто – если в документе есть пробелы (или любые другие символы), они становятся текстовыми узлами дерева DOM, и если мы их удалим, то в DOM их тоже не будет.

Здесь пробельных текстовых узлов нет:

 О лосяхПравда о лосях.

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

Когда мы работаем с деревом DOM, используя инструменты разработчика в браузере (которые мы рассмотрим позже), пробелы в начале/конце текста и пустые текстовые узлы (переносы строк) между тегами обычно не отображаются.

Таким образом инструменты разработки экономят место на экране.

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

Автоисправление

Если браузер сталкивается с некорректно написанным HTML-кодом, он автоматически корректирует его при построении DOM.

Например, в начале документа всегда должен быть тег . Даже если его нет в документе – он будет в дереве DOM, браузер его создаст. То же самое касается и тега .

Например, если HTML-файл состоит из единственного слова «Привет» , браузер обернёт его в теги и , добавит необходимый тег , и DOM будет выглядеть так:

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

Есть такой документ с незакрытыми тегами:

…Но DOM будет нормальным, потому что браузер сам закроет теги и восстановит отсутствующие детали:

Таблицы всегда содержат

Важный «особый случай» – работа с таблицами. По стандарту DOM у них должен быть , но в HTML их можно написать (официально) без него. В этом случае браузер добавляет в DOM самостоятельно.

Для такого HTML:

DOM-структура будет такой:

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

Другие типы узлов

Есть и некоторые другие типы узлов, кроме элементов и текстовых узлов.

Здесь мы видим узел нового типа – комментарий, обозначенный как #comment , между двумя текстовыми узлами.

Казалось бы – зачем комментарий в DOM? Он никак не влияет на визуальное отображение. Но есть важное правило: если что-то есть в HTML, то оно должно быть в DOM-дереве.

Все, что есть в HTML, даже комментарии, является частью DOM.

Даже директива , которую мы ставим в начале HTML, тоже является DOM-узлом. Она находится в дереве DOM прямо перед . Мы не будем рассматривать этот узел, мы даже не рисуем его на наших диаграммах, но он существует.

Даже объект document , представляющий весь документ, формально является DOM-узлом.

Существует 12 типов узлов. Но на практике мы в основном работаем с 4 из них:

  1. document – «входная точка» в DOM.
  2. узлы-элементы – HTML-теги, основные строительные блоки.
  3. текстовые узлы – содержат текст.
  4. комментарии – иногда в них можно включить информацию, которая не будет показана, но доступна в DOM для чтения JS.

Поэкспериментируйте сами

Чтобы посмотреть структуру DOM в реальном времени, попробуйте Live DOM Viewer. Просто введите что-нибудь в поле, и ниже вы увидите, как меняется DOM.

Другой способ исследовать DOM – это использовать инструменты разработчика браузера. Это то, что мы каждый день делаем при разработке.

Для этого откройте страницу elks.html, включите инструменты разработчика и перейдите на вкладку Elements.

Выглядит примерно так:

Вы можете увидеть DOM, понажимать на элементы, детально рассмотреть их и так далее.

Обратите внимание, что структура DOM в инструментах разработчика отображается в упрощённом виде. Текстовые узлы показаны как простой текст. И кроме пробелов нет никаких «пустых» текстовых узлов. Ну и отлично, потому что большую часть времени нас будут интересовать узлы-элементы.

Клик по этой кнопке в левом верхнем углу инспектора позволяет при помощи мыши (или другого устройства ввода) выбрать элемент на веб-странице и «проинспектировать» его (браузер сам найдёт и отметит его во вкладке Elements). Этот способ отлично подходит, когда у нас огромная HTML-страница (и соответствующий ей огромный DOM), и мы хотим увидеть, где находится интересующий нас элемент.

Есть и другой способ сделать это: можно кликнуть на странице по элементу правой кнопкой мыши и в контекстном меню выбрать «Inspect».

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

  • Styles – здесь мы видим CSS, применённый к текущему элементу: правило за правилом, включая встроенные стили (выделены серым). Почти всё можно отредактировать на месте, включая размеры, внешние и внутренние отступы.
  • Computed – здесь мы видим итоговые CSS-свойства элемента, которые он приобрёл в результате применения всего каскада стилей (в том числе унаследованные свойства и т.д.).
  • Event Listeners – в этом разделе мы видим обработчики событий, привязанные к DOM-элементам (мы поговорим о них в следующей части учебника).
  • … и т.д.

Лучший способ изучить инструменты разработчика – это прокликать их. Большинство значений можно менять и тут же смотреть результат.

Взаимодействие с консолью

При работе с DOM нам часто требуется применить к нему JavaScript. Например: получить узел и запустить какой-нибудь код для его изменения, чтобы посмотреть результат. Вот несколько подсказок, как перемещаться между вкладками Elements и Console.

  1. На вкладке Elements выберите первый элемент
  2. .
  3. Нажмите Esc – прямо под вкладкой Elements откроется Console.

Последний элемент, выбранный во вкладке Elements, доступен в консоли как $0 ; предыдущий, выбранный до него, как $1 и т.д.

Теперь мы можем запускать на них команды. Например $0.style.background = ‘red’ сделает выбранный элемент красным, как здесь:

Это мы посмотрели как получить узел из Elements в Console.

Есть и обратный путь: если есть переменная node , ссылающаяся на DOM-узел, можно использовать в консоли команду inspect(node) , чтобы увидеть этот элемент во вкладке Elements.

Или мы можем просто вывести DOM-узел в консоль и исследовать «на месте», как document.body ниже:

Это может быть полезно для отладки. В следующей главе мы рассмотрим доступ и изменение DOM при помощи JavaScript.

Инструменты разработчика браузера отлично помогают в разработке: мы можем исследовать DOM, пробовать с ним что-то делать и смотреть, что идёт не так.

Итого

HTML/XML документы представлены в браузере в виде DOM-дерева.

  • Теги становятся узлами-элементами и формируют структуру документа.
  • Текст становится текстовыми узлами.
  • … и т.д. Всё, что записано в HTML, есть и в DOM-дереве, даже комментарии.

Для изменения элементов или проверки DOM-дерева мы можем использовать инструменты разработчика в браузере.

Здесь мы рассмотрели основы, наиболее часто используемые и важные действия для начала разработки. Подробную документацию по инструментам разработки Chrome Developer Tools можно найти на странице https://developers.google.com/web/tools/chrome-devtools. Лучший способ изучить инструменты – походить по разным вкладкам, почитать меню: большинство действий очевидны для пользователя. Позже, когда вы немного их изучите, прочитайте документацию и узнайте то, что осталось.

У DOM-узлов есть свойства и методы, которые позволяют выбирать любой из элементов, изменять, перемещать их на странице и многое другое. Мы вернёмся к ним в последующих разделах.

Соглашение по именованию

Имя БЭМ-сущности уникально. Во всех технологиях (CSS, JavaScript, HTML) одна и та же БЭМ-сущность всегда называется одинаково. Основная идея соглашения по именованию — вложить смысл в имена и сделать их максимально информативными для разработчика.

Можно сравнить одно и тоже имя CSS-селектора, написанное разными способами:

  • menuitemvisible
  • menu-item-visible
  • menuItemVisible

Чтобы понять смысл первого имени, нужно вчитаться в каждое слово. В последних двух примерах имя явно разделяется на логические части. Но ни одно из имен пока не помогает точно определить, что menu — это блок, item — элемент, а visible — модификатор. Чтобы имена сущностей были однозначными и понятными, в БЭМ были разработаны правила формирования имен БЭМ-сущностей.

Правила формирования имен

  • Имена записываются латиницей в нижнем регистре.
  • Для разделения слов в именах используется дефис ( — ).
  • Имя блока задает пространство имен для его элементов и модификаторов.
  • Имя элемента отделяется от имени блока двумя подчеркиваниями ( __ ).
  • Имя модификатора отделяется от имени блока или элемента одним подчеркиванием ( _ ).
  • Значение модификатора отделяется от имени модификатора одним подчеркиванием ( _ ).
  • Значение булевых модификаторов в имени не указывается.

Важно! В методологии БЭМ не существует элементов элементов. Правила именования запрещают создавать элементы элементов, но в DOM-дереве элементы можно вкладывать друг в друга.

Примеры

В HTML БЭМ-сущности представлены атрибутом class . В БЭМ во всех технологиях происходит обращение к классу:

Примеры правил формирования имен приведены на CSS.

Имя блока
div class="menu">. div> 
.menu < color: red; >
Имя элемента

Важно! Имена одинаковых элементов одного и того же блока совпадают. Например, все пункты в блоке меню называются menu__item .

div class="menu"> . span class="menu__item"> span> div> 
.menu__item < color: red; >
Имя модификатора блока

menu_theme_islands

div class="menu menu_hidden">. div> div class="menu menu_theme_islands">. div> 
.menu_hidden < display: none; > .menu_theme_islands < color: green; >
Имя модификатора элемента

menu__item_type_radio

div class="menu"> . span class="menu__item menu__item_visible menu__item_type_radio">. span> div> 
.menu__item_visible <> .menu__item_type_radio < color: blue; >

Альтернативные схемы именования

Описанные выше правила формирования имен — это классическая схема именования БЭМ-сущностей. Все инструменты БЭМ по умолчанию настроены на классическую схему.

Существуют альтернативные схемы именования, которые активно используются в БЭМ-сообществе. Чтобы во всех технологиях применять одинаковые имена, созданные по альтернативным схемам, используйте инструмент bem-naming. По умолчанию bem-naming содержит настройки соглашения по именованию, предложенного методологией, но позволяет добавлять правила для применения альтернативных схем.

Стиль Two Dashes

  • Имена записываются латиницей в нижнем регистре.
  • Для разделения слов в именах БЭМ-сущностей используется дефис ( — ).
  • Имя элемента отделяется от имени блока двумя подчеркиваниями ( __ ).
  • Булевые модификаторы отделяются от имения блока или элемента двумя дефисами ( — ).
  • Значение модификатора отделяется от его имени двумя дефисами ( — ).

Важно! Двойной дефис внутри комментария ( — ) может вызвать ошибки при валидации HTML-документа.

Стиль CamelCase

  • Имена записываются латиницей.
  • Каждое слово внутри имени пишется с заглавной буквы.
  • Разделители элементов и модификаторов совпадают с классической схемой.

Стиль React

  • Имена записываются латиницей.
  • Имена блоков и элементов пишутся с заглавной буквы. Имена модификаторов — со строчной.
  • Каждое слово внутри имени пишется с заглавной буквы.
  • Имя элемента отделяется от имени блока одним дефисом ( — ).
  • Разделители имени и значения модификаторов совпадают с классической схемой.

Стиль No-namespace

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

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

Ваша схема именования

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

Навигация по DOM-дереву — JS: DOM API

Знакомство с DOM-деревом проще всего начать с изучения структуры этого дерева.

Если коротко, DOM-дерево состоит из узлов (нод, node). Вместе узлы образуют иерархию, аналогичную HTML. При этом узлы делятся на два типа:

  • Листовые — не содержат внутри себя других узлов
  • Внутренние – у них есть узлы

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

Корневой элемент в DOM-дереве соответствует тегу . Доступ к нему можно получить так:

const html = document.documentElement; // Свойство tagName узла содержит имя тега в верхнем регистре console.log(html.tagName); // => 'HTML' // Содержимое тега HTML в виде узлов DOM-дерева // Текст тоже представлен узлом html.childNodes; // [head, text, body] // Потому что head выше body html.firstChild; // .  html.lastChild; // .  // Второй узел, обращение по индексу html.childNodes[1]; // #text 

Теги и всегда присутствуют внутри документа, поэтому можно вынести их на уровень объекта document для более простого доступа:

document.head; document.body; 

По дереву можно не только спускаться, но и подниматься:

// Родитель body это html document.documentElement === document.body.parentNode; // true document.body === document.body.childNodes[2].parentNode; // true 

Если представить дерево, то по нему можно двигаться как вверх-вниз, так и влево-вправо. Картинка ниже это демонстрирует:

childNodes

Далее мы рассмотрим childNodes – свойство, с помощью которого можно получить дочерние узлы — это узлы, вложенные в текущий узел на один уровень вложенности. Еще говорят, что это потомки первого уровня.

В работе с childNodes есть несколько интересных моментов:

    Это свойство доступно только для чтения. Попытка что-то записать в конкретный элемент не приведет к успеху:

// Ошибки не будет, но ничего не поменяется document.body.childNodes[0] = 'hey'; 
// Тип NodeList const nodes = document.documentElement.childNodes; nodes.forEach((el) => console.log(el)); 

Если очень хочется, то его можно преобразовать в массив, и затем уже работать привычным способом:

const list = Array.from(nodes); // Теперь у нас обычный массив и доступные методы, например, filter // Можем отфильтровать нужные элементы const filtered = list.filter((item) => item.textContent.includes('Навигация по DOM-дереву')); // И извлечь из них данные, например, имена тегов const filteredTags = filtered.map((item) => item.tagName); console.log(filteredTags); // => ['HEAD', 'BODY'] 

Иерархия

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

// Самый простой способ посмотреть тип document.body.toString(); // "[object HTMLBodyElement]" document.body instanceof HTMLBodyElement; // true 

Узлы с типами Text и Comment являются листовыми, то есть они не могут иметь потомков. А вот элементы или производные типы от Element — это то, с чем приходится иметь дело чаще всего. К элементам относятся все типы, представленные тегами в HTML.

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

Посмотрите на пример кода:

В этом примере тег (с id parent-div ) содержит 14 потомков и три дочерних узла. Разберемся, в чем разница между этими понятиями.

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

  • Текст «Привет!»
  • с классом child-div

Потомками называют все вложенные узлы на всех уровнях вложенности. Потомками тега (с id parent-div ) будут не только три вышеупомянутых дочерних тега, но и все узлы внутри них:

Дочерние узлы одновременно являются потомками. Обратное утверждение неверно — потомок необязательно является дочерним элементом. В нашем примере тег приходится потомком, но не дочерним элементом по отношению к тегу с id parent-div .

Элементы

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

Все эти свойства возвращают объекты типа Element и пропускают объекты Text или Comment. Это видно в примере ниже, где свойство children возвращает только теги.

Этим children отличается от childNodes , который возвращает все узлы, включая листовые:

const node = document.documentElement; node.children; // [head, body] 

Между children и childNodes есть еще одно довольно важное отличие. Они возвращают не только разный набор узлов, но и сам тип коллекции в первом и втором случае разный:

  • childNodes возвращает NodeList
  • children — HTMLCollection

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

Специальная навигация

Некоторые элементы обладают специальными свойствами для навигации по ним, к таким элементам относятся, например, формы и таблицы:

   1.1 1.2 1.3   2.1 2.2 2.3   
const table = document.body.firstElementChild table.rows[0].cells[2]; 

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

Заключение

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Как работают браузеры. Часть 2: парсинг и выполнение JS

В прошлой статье мы обсудили навигацию и получение данных. Сегодня поговорим о HTML- и CSS-парсинге и выполнении JavaScript.

1. HTML-Парсинг

Мы видели, как после первоначального запроса к серверу браузер получает ответ c HTML-ресурсами страницы, к которой мы пытаемся получить доступ. Это первая порция данных. Теперь задача браузера – начать парсинг данных.

Парсинг

��Анализ и преобразование программы во внутренний формат, который может запустить среда выполнения.

Другими словами, это получение HTML/CSS-кода и преобразование его в то, с чем браузер может работать. Парсинг выполняется движком браузера (не путать с движком JavaScript).

Браузерные движки

Движок браузера — основной компонент каждого крупного браузера. Его основная роль — объединить структуру (HTML) и стиль (CSS), чтобы отобразить страницу на экране. Он также отвечает за выяснение того, какие фрагменты кода интерактивны. Не думайте о нем как об отдельном ПО. Скорее, это часть более крупного ПО — в нашем случае, браузера.

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

Gecko. Разработан компанией Mozilla для браузера Firefox. Кроме него, сейчас движок Gecko используют ещё в Tor и Waterfox. Написан на C++ и Rust.

WebKit. В основном его разработала компания Apple для Safari. На нем также работают браузеры GNOME Web (Epiphany) и Otter. Интересно, что все браузеры на iOS, включая Firefox и Chrome, работают на WebKit. Он написан на C++.

Blink, часть Chromium. Разрабатывает компания Google на основе кода WebKit для браузера Chrome. На нем также работают браузеры Edge, Brave, Silk, Vivaldi, Opera и большинство других проектов браузеров, некоторые через QtWebEngine. Написан на C++.

С этим понятно. Теперь посмотрим, что произойдет после получения первого HTML-документа с сервера. Предположим, документ выглядит так:

   This is my page  

This is my page

This is a H3 header.

This is a paragraph.

This is another paragraph,

Даже если HTML-код страницы запроса больше исходного пакета 14 КБ, браузер начнет парсинг и попытается отобразить результат на основе имеющихся данных.

HTML-парсинг включает два этапа: токенизацию и построение DOM-дерева. Document Object Model – объектная модель документа.

Токенизация

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

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

Построение DOM

После создания первого токена начинается создание древовидной структуры (объектной модели документа) на основе ранее проанализированных токенов.

��DOM-дерево описывает содержимое документа HTML. Элемент — первый тег и корневой узел дерева документа. Дерево отражает отношения и иерархии между различными тегами.

Теги делятся на родительские узлы и дочерние — теги, вложенные в другие теги. Чем больше узлов, тем больше времени потребуется для построения DOM-дерева. Ниже показано DOM-дерево для примера HTML-документа, полученного с сервера:

Это упрощенная схема, и в реальности DOM сложнее. О его важности мы поговорим чуть позже.

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

Парсер обрабатывает строку за строкой сверху вниз. Когда он наталкивается на неблокирующие ресурсы (например, изображения), браузер запрашивает их с сервера и продолжает парсинг. Если наталкивается на блокирующие, останавливает выполнение, пока они все не загрузятся. Это могут быть таблицы стилей CSS, файлы JavaScript, добавленные в раздел HTML, или шрифты, добавленные из CDN. Поэтому если работаете с JavaScript, рекомендуется добавить теги в конце HTML-файла или, если хотите сохранить их в теге , добавьте к ним атрибут defer или async . async разрешает асинхронность сразу после загрузки скрипта, а defer разрешает выполнение только после анализа всего документа.

Предварительные загрузчики и ускорение страницы

В браузерах Internet Explorer, WebKit и Mozilla предварительные загрузчики появились в 2008 году как способ работы с блокирующими ресурсами, особенно скриптами. Выше мы разобрали, что при столкновении с тегом скрипта HTML-парсинг прекращается, пока не загрузится и не запустится скрипт.

С предварительным загрузчиком схема выглядит так: когда браузер застревает на скрипте, второй более легкий парсер сканирует HTML на необходимые ресурсы — таблицы стилей, скрипты и т.д. Затем предварительный загрузчик извлекает ресурсы в фоновом режиме, чтобы к моменту, когда основной HTML-парсер достигнет их, их уже можно было загрузить. Если эти ресурсы уже кэшированы, этап пропускается.

2. CSS-Парсинг

Теперь проанализируем CSS (во внешних CSS-файлах и в элементах стиля) и построим CSSOM-дерево (объектная модель CSS).

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

Токенизация и построение CSSOM

Как и HTML, CSS-парсинг начинается с токенизации. Парсер берет байты и преобразует их в символы, затем в токены, узлы и, наконец, они связываются в CSSOM. Браузер производит селекторное согласование, чтобы каждый набор стилей соответствовал всем элементам на странице.

Браузер начинает с общего правила, применимого к узлу. Например: если узел — дочерний по отношению к элементу body, то он наследует все стили body. Затем браузер рекурсивно уточняет вычисляемые стили, применяя более конкретные правила. Вот почему правила стиля называют каскадными.

Для примера возьмем такой CSS:

body < font-size: 16px; color: white; >h1 < font-size: 32px; >section < color: tomato; >section .mainTitle < margin-left: 5px >div < font-size: 20px; >div p

CSSOM для данного кода примерно такой:

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

  • унаследованные — от родительского, например: h1 наследует цвет от body , а section наследует размер шрифта от body
  • собственные стили могут переписывать правила, унаследованные от родительского элемента, например: p переписывает цвет и размер шрифта, унаследованный от div

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

Представьте, что вы в аэропорту ищете друга Ваню. Если хотите найти его по имени, можно крикнуть: «Ваня!». Скорее всего, в аэропорту будет несколько Вань, и они все откликнутся. Лучше позвать друга полным именем. Если крикнуть «Ваня Иванов», шансов найти друга будет больше.

Аналогично возьмем такой элемент…

… и такие стили CSS:

a < color: red; >p a

Как думаете, какое правило применится в браузере?

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

Правила CSS читаются справа налево, то есть если у нас есть такое: section p < color: blue; >, браузер сначала ищет все теги p на странице, а затем смотрит, есть ли у этих тегов p родительский тег section . В таком случае применяется правило CSS.

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

3. Выполнение JavaScript

Итак, после получения файла JavaScript с сервера код интерпретируется, компилируется, анализируется и выполняется. Код JS преобразуется в то, с чем компьютер может работать, и это задача движка JavaScript. В зависимости от браузера движки JS называются и работают по-разному.

Движки JavaScript

�� Движок JavaScript (иногда его называют движком ECMAScript) — часть программного обеспечения, которая выполняет код JavaScript в браузере, и не только. Например, движок V8 — основной компонент среды Node.js.

Движки JavaScript, как правило — продукт деятельности разработчиков веб-браузеров. Мы говорили, что самые популярные браузеры — Chrome, Safari, Edge и Firefox. У каждого из них свой движок JavaScript:

V8. Высокопроизводительный движок JavaScript компании Google. Он написан на C++ и используется, в частности, в браузере Chrome и платформе Node.js. Реализует стандарты ECMA-262, ECMA-402 и WebAssembly.

JavaScriptCore. Встроенный движок JavaScript для WebKit, на котором работает Safari, Mail и другие приложения на macOS. В настоящее время он реализует спецификацию ECMA-262.

Chakra. Движок JavaScript, разработанный компанией Microsoft для браузера Microsoft Edge и других приложений Windows. Он реализует ECMA-262 версии 5.1 и частично поддерживает версию 6.

SpiderMonkey. Движок JavaScript и WebAssembly компании Mozilla. Он написан на C++, JavaScript и Rust и используется в Firefox, Servo и других проектах.

В начале движки JavaScript были простыми интерпретаторами. Современные браузеры проводят так называемую компиляцию Just-In-Time (JIT), сочетание компиляции и интерпретации.

Компиляция

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

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

Интерпретация

Такой тип исполнения кода используется в старых версиях JS.

Во время работы интерпретатор просматривает код JavaScript строку за строкой и немедленно выполняет его. Компиляции нет, поэтому объектный код не создается. Сам интерпретатор создает выходной код, используя внутренние механизмы.

Компиляция Just-In-Time

Такой тип исполнения кода используется в новейших версиях JavaScript.

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

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

Если говорить совсем упрощенно, эти три процесса сводятся к следующему:

  • Компилятор преобразует код
  • Интерпретатор запускает код
  • Компилятор JIT преобразует во время выполнения кода

Сегодняшняя грань между терминами компиляция и интерпретация сильно стерлась. Если хотите узнать больше, для начала прочитайте статью на Mozilla Hacks (EN).

Обратите внимание, что я упомянул разные версии стандарта JavaScript. Браузеры, не поддерживающие новые версии языка, интерпретируют код, в то время как поддерживающие используют JIT для выполнения кода: движки V8, Chakra JavaScriptCore и SpiderMonkey. Хотя JavaScript — интерпретируемый язык и ему не нужна компиляция, большинство браузеров используют JIT-компиляцию для запуска кода, а не чистую интерпретацию.

Как обрабатывается код JavaScript

Когда код JavaScript принимает код, сначала проводится его анализ. Код считывается и одновременно преобразуется в структуру данных под названием абстрактное синтаксическое дерево AST. Код разделяется на фрагменты, имеющие отношение к языку. Например, ключевые слова function или const . Затем все эти фрагменты построят абстрактное синтаксическое дерево.

Допустим, у нас есть файл с программой, которая выполняет только одно действие — определяет переменную:

const age = 25;

В этом случае абстрактное синтаксическое дерево будет выглядеть так (используется @babel/parser-7.16.12:

Если хотите преобразовать JavaScript в абстрактное синтаксическое дерево самостоятельно, используйте AST explorer. AST, полученный после записи переменной, на самом деле намного больше: в нём больше узлов, которые скрыты на снимке экрана.

После построения AST переводится в машинный код и выполняется сразу, так как современный JavaScript использует JIT-компиляцию.

Движок JavaScript выполняет этот код с использованием стека вызовов.

��Стек вызовов — механизм, с помощью которого интерпретатор JavaScript определяет, какая функция выполняется в настоящее время, какие функции вызываются из этой функции и т.д.

Ссылки на все материалы из статьи:

  • Parsing (EN) — MDN Web Docks
  • HTML Standard Parsing (EN)
  • DOM-дерево — javascript.ru
  • Объектная Модель Документа — MDN Web Docs
  • Краткая история CSS до 2016 г. (EN)
  • Как работают браузеры — MDN Web Docs
  • SpiderMonkey
  • ChakraCore
  • JavaScriptCore
  • V8
  • Node.js V8
  • Стек вызовов — MDN Web Docs

Другие статьи про frontend для начинающих:

  • Как сделать свой текстовый редактор на React.js
  • Как работают браузеры: навигация и получение данных, парсинг и выполнение JS, деревья спец возможностей и рендеринга

Другие статьи про frontend для продвинутых:

  • Как мы выбирали архитектуру микрофронтендов в ЛК для 260 000 сотрудников Пятёрочки
  • Как мы сетапили монорепозиторий с SSR и SPA для Otus.ru
  • Как собрать свою библиотеку для SSR на React

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *