Как работает браузер при вводе запроса и этапы рендера
Поиск в кэше: Браузер проверяет свой кэш, чтобы узнать, есть ли уже соответствие между доменом и IP-адресом.
Если в кэше браузера нет записи, запрос отправляется в операционную систему. ОС проверяет файл hosts, который может содержать статические записи доменов и IP-адресов.
Запрос к DNS серверу: Если и в файле hosts нет записи, браузер отправляет запрос на DNS-сервер, чтобы получить соответствие домену. DNS-сервер возвращает IP-адрес, который браузер сохраняет в кэш на определённое время.
Установка соединения через TCP (трехстороннее рукопожатие)
рукопожатие (TCP handshake):
SYN: Клиент отправляет запрос на установление соединения с сервером.
SYN-ACK: Сервер отвечает подтверждением на запрос клиента.
ACK: Клиент подтверждает получение ответа, соединение установлено.
Это согласование позволяет установить параметры соединения, такие как скорость и размер пакетов.
Отправка HTTP-запроса
Когда соединение установлено, браузер отправляет HTTP GET-запрос на сервер для получения запрашиваемого ресурса (обычно HTML-документа).
Получение и парсинг HTML
Когда сервер отправляет HTML-документ в ответ на запрос, браузер начинает его парсить и строить DOM (Document Object Model) дерево — структуру, представляющую HTML-страницу как объектную модель.
Загрузка и парсинг CSS и JS
CSS: В процессе парсинга HTML браузер начинает загружать и парсить файлы CSS, создавая CSSOM (CSS Object Model) — модель стилей, которая содержит информацию о том, как стили применяются к элементам на странице.
JS: Когда браузер встречает тег
, он должен выполнить JavaScript код. Скрипты могут блокировать дальнейший рендеринг страницы, так как они могут изменять DOM или CSSOM, поэтому JavaScript код выполняется синхронно до продолжения рендеринга.
Важно: Если скрипты добавляются с атрибутом async или defer, они не блокируют рендеринг, так как выполняются после загрузки страницы.
Формирование Render Tree (Дерево рендеринга)
На основе DOM и CSSOM браузер строит render tree — дерево рендеринга, которое описывает, как элементы будут отображаться на экране. Это дерево включает в себя визуальные элементы (например, блоки, изображения, текст), но не включает элементы, которые не отображаются (например, <head>, display: none).
Layout (Позиционирование элементов)
На этом этапе браузер рассчитывает расположение каждого элемента на странице:
Браузер вычисляет положение элементов (например, с помощью моделей коробок — box model) и их размеры (ширина, высота).
Рассчитываются отступы (margin), границы (border), внутренние отступы (padding), а также позиционирование элементов на странице.
Этап layout необходим для того, чтобы точно определить, где и как должны располагаться элементы на экране.
Painting (Отрисовка)
На этапе painting браузер “рисует” каждый элемент на экране, основываясь на данных из дерева рендеринга. Это включает в себя отображение цветов, текстур, шрифтов, границ и других визуальных свойств.
Каждый элемент “рисуется” с учётом его стилей, а текст может быть нарисован как отдельные строки или блоки.
Composition (Составление слоёв)
Когда элементы страницы нарисованы, браузер переходит к последнему этапу — composition. Здесь происходит составление слоёв:
Браузер группирует элементы в слои (layers), чтобы эффективно обработать их. Эти слои могут быть отрисованы независимо, что позволяет ускорить рендеринг.
Важные стили, такие как transform или opacity, могут использоваться для переноса элементов на GPU для дальнейшей обработки, чтобы разгрузить процессор.
На этом этапе браузер может оптимизировать рендеринг, отправив слои на видеокарту для отрисовки.
Отличия any и unknown в TypeScript
Тип any используется, когда вы хотите указать, что значение может быть любым типом.
Преимущества использования any
Гибкость:
Удобство:
Недостатки использования any
Потеря типовой безопасности:
Усложнение поддержки кода
Тип unknown — это более безопасная альтернатива any. С unknown вы по-прежнему можете работать с переменной любого типа, но для этого необходимо проверить её тип перед выполнением операций с ней. То есть, в отличие от any, с переменной типа unknown не получится выполнять операции без проверки её типа.
Преимущества использования unknown
Типовая безопасность: хотя unknown позволяет работать с любыми типами, при этом компилятор TypeScript требует, чтобы вы выполнили проверку типа перед тем, как выполнить какие-либо операции с значением.
Является более безопасным выбором:
Не такая гибкость как у any: перед использованием переменной типа unknown требуется выполнить проверку её типа, что добавляет дополнительный код.
Как работает keyof и typeof в TypeScript
keyof и typeof — это два оператора в TypeScript, которые используются для работы с типами данных и для улучшения типизации. Они позволяют работать с типами объектов и переменных, а также извлекать ключи и типы из существующих значений.
Оператор keyof
Оператор keyof позволяет извлечь все ключи объекта в виде строкового объединения. Это полезно, когда вам нужно работать с объектами и проверять ключи, доступные для них, или когда вы хотите типизировать переменные, которые должны быть одним из ключей объекта.
Оператор typeof
Оператор typeof позволяет извлечь тип переменной или значения. Это полезно, когда вам нужно узнать тип переменной, не указывая его явно, и использовать этот тип в других частях кода.
Строгий режим (strict mode) в JavaScript
Строгий режим (strict mode) в JavaScript — это специальный режим работы, который помогает писать более безопасный и качественный код. Включив его, вы активируете дополнительные проверки и ограничения, которые предотвращают распространённые ошибки.
Что меняет строгий режим?
Запрещает использование необъявленных переменных.
Запрещает удаление переменных, функций или объектов.
Ограничивает работу с this в функциях. Без строгого режима:
Set, Map, WeakSet и WeakMap
Set— это коллекция уникальных значений, где каждое значение может появляться только один раз.
Хранит только уникальные значения.
Значения могут быть любого типа.
Неупорядоченная структура
Map — это коллекция пар ключ-значение, где ключи могут быть любого типа.
Упорядоченная структура.
Позволяет использовать объекты в качестве ключей.
WeakSet — это коллекция, в которой хранятся только объекты. Эти объекты являются слабо-ссылаемыми, что позволяет сборщику мусора удалять их, если на них нет других ссылок.
Хранит только объекты.
Нельзя получить размер или перебрать элементы.
Используется для временного хранения объектов.
Как работает Event Loop?
Event Loop — это механизм в JavaScript, который управляет асинхронными задачами и очередями событий. Он позволяет JavaScript работать в однопоточной модели, обрабатывая асинхронные операции, не блокируя основной поток выполнения.
Как работает Event Loop?
Event Loop — это бесконечный цикл, в котором выполняются обработчики событий. В процессе его работы браузер распределяет задачи по двум основным очередям:
Микротаски — это задачи, такие как промисы, queueMicrotask(), MutationObserver.
Макротаски — это задачи, такие как setTimeout, события, таймеры, запросы fetch.
Основной порядок выполнения
Сначала выполняются все синхронные задачи из стека вызовов (Call Stack).
Затем выполняются все задачи из очереди микротасков. Все микротаски будут выполнены до того, как будет взята следующая макрозадача. Микротаски блокируют Event Loop, пока все не будут выполнены.
После выполнения всех микротасков очередь очищается.
Далее из очереди макротасков берется одна макрозадача и выполняется. Макрозадачи, такие как setTimeout, ждут, пока все синхронные задачи и микротаски будут завершены.
После выполнения макрозадачи проверяется, нужно ли выполнить перерисовку страницы. Если необходимо, браузер выполняет рендеринг.
Повторный цикл. Event Loop продолжает обрабатывать задачи, пока не закончится выполнение всех операций.
Структуры данных
Call Stack (LIFO — Last In, First Out):
Стек вызовов содержит все функции, которые выполняются в текущий момент.
Когда вызывается функция, она добавляется в стек. Если внутри функции вызывается другая функция, она тоже добавляется в стек.
После выполнения функции она удаляется из стека.
Web API:
Асинхронные операции, такие как setTimeout, обработчики событий, запросы fetch, попадают в Web API.
После завершения работы асинхронной задачи, она не сразу попадает в Call Stack. Вместо этого она помещается в Callback Queue (очередь обратных вызовов).
Callback Queue (FIFO — First In, First Out):
Здесь хранятся функции, которые должны быть выполнены после того, как стек вызовов окажется пустым.
Когда стек вызовов очищается, Event Loop перемещает задачи из Callback Queue в Call Stack и выполняет их.
Микротаски vs. Макротаски
Макротаски:
Пример: setTimeout, события (onClick, onChange), запросы fetch.
Эти задачи обрабатываются в порядке очереди, одна за другой, после того как выполняются все синхронные задачи и микротаски.
Микротаски:
Пример: промисы, queueMicrotask(), MutationObserver.
Микротаски выполняются сразу после выполнения синхронных операций и до выполнения макротасок.
Все микротаски из очереди выполняются перед следующими макрозадачами.
Промисы в JavaScript
Promise — это функция-конструктор, которая используется для создания промисов в JavaScript. Промис представляет собой объект, который позволяет работать с асинхронными операциями и выполнять их без блокировки основного потока выполнения.
Promise.all() используется для выполнения нескольких промисов параллельно и возвращает новый промис, который выполнится, когда все промисы из массива завершатся успешно. Если хотя бы один промис отклонится, результат будет отклонен с ошибкой того промиса, который отклонился.
Promise.race() принимает массив промисов и возвращает новый промис, который завершится как только завершится первый из промисов в массиве (независимо от того, был ли он выполнен успешно или с ошибкой).
Promise.allSettled() выполняет все промисы в массиве и возвращает результат для каждого, независимо от того, были ли они выполнены или отклонены.
Promise.any() возвращает новый промис, который выполнится, как только первый промис из массива завершится успешно. Если все промисы отклоняются, он отклонится с ошибкой.
Что такое проваливание промисов (Promise Chaining)
— это механизм, при котором каждый .then() возвращает новый промис, позволяя выстраивать цепочку асинхронных операций.
fetch(“/user.json”)
.then(response => response.json()) // промис №1
.then(user => fetch(/users/${user.id})) // промис №2
Что такое прототип?
— это объект, к которому обращается любой другой объект в случае, если у него нет искомого свойства или метода. Если при обращении к свойству его нет в самом объекте, интерпретатор будет «подниматься» снизу-вверх по цепочке прототипов (prototype chain), пока не найдёт нужное свойство или не достигнет объекта null.
— это механизм, позволяющий одним объектам наследовать свойства и методы других объектов.
Что такое объект Proxy в JavaScript
Proxy — это встроенный объект в JavaScript, позволяющий перехватывать и переопределять поведение других объектов. Вы можете «обернуть» объект и управлять доступом к его свойствам, их изменением, удалением, вызовом функций и многим другим.
Проще говоря, Proxy — это как «прослойка» между объектом и его использованием.
Лексическое окружение (Lexical Environment) в JavaScript
Лексическое окружение (Lexical Environment) — это внутренняя структура, которая используется движком JavaScript для хранения переменных, объявленных в определённой области видимости, а также для определения того, где и как эти переменные можно использовать.
Каждая функция, блок {}, и глобальная область создают своё лексическое окружение при выполнении.
Статические методы в JavaScript (Static Methods)
— это методы, которые определяются на уровне класса и доступны только через сам класс, а не через его экземпляры. Такие методы предназначены для реализации функциональности, которая не зависит от состояния конкретного объекта, и обычно используются для утилитарных операций,
Что такое каррирование (Currying)
— это техника преобразования функции с несколькими аргументами в последовательность функций, каждая из которых принимает по одному аргументу.
Зачем нужно каррирование?
Переиспользуемость: можно заранее зафиксировать часть аргументов.
Чистота функций: каррированные функции легче тестировать и комбинировать.
Функциональный стиль: часто применяется в функциональном программировании.
Отличие event.target и event.currentTarget
event.target — элемент, на котором фактически произошло событие. Это может быть вложенный элемент.
event.currentTarget — элемент, на котором висит обработчик события
Разница между оператором in и методом hasOwnProperty()
in — оператор, проверяющий всё (включая унаследованные свойства).
.hasOwnProperty() — метод объекта, проверяющий только собственные свойства.
Что такое делегирование событий?
— это техника в JavaScript, при которой обработчик события навешивается на родительский элемент, а не на каждый дочерний элемент отдельно.
<ul>
<li>Главная</li>
<li>О нас</li>
<li>Контакты</li>
</ul>
const menu = document.getElementById(“menu”);
menu.addEventListener(“click”, (event) => {
const target = event.target;
if (target.tagName === “LI”) {
console.log(“Вы кликнули по пункту:”, target.textContent);
}
});
Что такое генераторы в JavaScript?
Генераторы — это функции, которые могут приостанавливать своё выполнение и возобновлять его позже. Они дают больше контроля над потоком выполнения, в отличие от обычных функций.
Генераторы определяются с помощью function* и управляются через метод .next().
Синтаксис генератора
function* generatorFunction() {
yield ‘Первое значение’;
yield ‘Второе значение’;
return ‘Финал’;
}
const gen = generatorFunction();
console.log(gen.next()); // { value: ‘Первое значение’, done: false }
console.log(gen.next()); // { value: ‘Второе значение’, done: false }
console.log(gen.next()); // { value: ‘Финал’, done: true }
Как работает генератор
yield — ключевое слово, приостанавливает выполнение и возвращает значение.
.next() — возобновляет выполнение с места последнего yield.
done: true — указывает, что генератор завершён.
Отличия от обычных функций
Особенность Обычные функции Генераторы
Возврат значений Только один return Много yield
Промежуточные состояния Нет Да
Управление потоком Нет Да
Использование for..of Нет Да
Что такое полифил (polyfill)?
— это код (обычно JavaScript), который реализует функциональность, отсутствующую в старых браузерах, но уже доступную в современных версиях.
Другими словами: это «заплатка», которая добавляет поддержку нового API там, где её ещё нет.
Зачем нужен оператор instanceof
Оператор instanceof в JavaScript проверяет, принадлежит ли объект определённому классу (или функции-конструктору) в его цепочке прототипов.
requestAnimationFrame и requestIdleCallback
requestAnimationFrame — это метод браузера, который позволяет выполнить функцию перед следующим перерисовыванием кадра. Используется в основном для создания плавных анимаций и оптимизации рендеринга.
requestIdleCallback — это метод, который позволяет выполнять задачи, когда браузер “свободен” — в периоды простоя.
CSS Разница между script, async и defer
script (без атрибутов)
Скачивается и выполняется сразу, как только до него дойдет парсер HTML.
Блокирует парсинг HTML до завершения выполнения скрипта.
Подходит только для маленьких скриптов в конце страницы.
script async
Скрипт загружается асинхронно и выполняется сразу, как только загрузился.
Не гарантирует порядок выполнения (если несколько async-скриптов).
Подходит для сторонних скриптов (аналитика, реклама).
Поведение:
Не блокирует HTML-парсинг во время загрузки.
Может блокировать рендеринг при выполнении.
script defer
Скрипт загружается асинхронно, но выполняется после полной загрузки HTML.
Сохраняет порядок подключения скриптов.
Идеально для большинства современных приложений.
Поведение:
Не блокирует HTML.
Выполняется после завершения парсинга HTML, но до события DOMContentLoaded.
Что такое Prop Drilling
— это ситуация, когда данные (пропсы) передаются через несколько уровней вложенных компонентов, даже если промежуточные компоненты их не используют напрямую.
Методы жизненного цикла компонента в React
Жизненный цикл компонента в React — это последовательность этапов, через которые проходит компонент от его создания до удаления из DOM.
Жизненный цикл существует только у классовых компонентов, но его поведение можно повторить с помощью хуков в функциональных компонентах.
Этапы жизненного цикла
Жизненный цикл делится на 3 ключевых фазы:
Фаза Описание
Mounting Момент появления компонента в DOM
Updating Обновление компонента при изменениях
Unmounting Удаление компонента из DOM
Методы Mounting-фазы
Метод Когда вызывается
constructor В момент создания компонента
static getDerivedStateFromProps До рендера (редко используется)
render Обязательный метод, возвращает JSX
componentDidMount После монтирования в DOM
Методы Updating-фазы
Метод Когда вызывается
static getDerivedStateFromProps При обновлении пропсов
shouldComponentUpdate Перед ререндером (можно отменить его)
render При каждом обновлении
getSnapshotBeforeUpdate Снимок DOM до обновления
componentDidUpdate После обновления компонента
Метод Unmounting-фазы
Метод Когда вызывается
componentWillUnmount Перед удалением компонента из DOM
Аналоги в функциональных компонентах
Функции-хуки позволяют повторить поведение жизненного цикла:
Классовый метод Хук в функции
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {…}, [dep])
componentWillUnmount useEffect(() => { return () => {…} }, [])
shouldComponentUpdate React.memo() + useCallback, useMemo
В React: React.lazy, Suspense, dynamic import().
Streaming (HTML Streaming, SSR Streaming)
Сервер постепенно отправляет HTML в браузер.
Позволяет начать рендер до того, как весь контент будет готов.
// Пример в React 18 с SSR streaming
import { renderToPipeableStream } from "react-dom/server";
Очень полезно в SSR и больших приложениях.
Skeletons и Placeholder'ы
Вместо пустых блоков показываются скелеты или заглушки, пока идёт загрузка данных.
{isLoading ?