Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.
Вопросы и ответы
32 подробных ответа
01Что такое box model и чем отличается content-box от border-box?
junior
Короткий ответ: Каждый элемент — это прямоугольник из четырёх слоёв: content (содержимое), padding (внутренний отступ), border (рамка), margin (внешний отступ). box-sizing определяет, входят ли padding и border в заданную width/height.
Подробно: Слои изнутри наружу: content → padding → border → margin.
content-box(значение по умолчанию):widthзадаёт ширину только содержимого. Реальная занимаемая ширина =width + padding + border.border-box:widthвключает в себяpaddingиborder. Реальная ширина = заданномуwidth. Так считать интуитивнее.
.a { box-sizing: content-box; width: 200px; padding: 20px; border: 5px solid; }
/* реальная ширина = 200 + 20*2 + 5*2 = 250px */
.b { box-sizing: border-box; width: 200px; padding: 20px; border: 5px solid; }
/* реальная ширина = 200px (content сжимается до 150px) */
Почти во всех проектах ставят глобально:
*, *::before, *::after { box-sizing: border-box; }
⚠️ Ловушка: margin НЕ входит в border-box — он всегда снаружи и добавляется к занимаемому месту. Также проценты для padding/margin всегда считаются от ширины родителя (даже вертикальные!) — это основа трюка с padding-top: 56.25% для сохранения соотношения сторон 16:9.
02В чём разница между block, inline, inline-block, none, flex, grid?
junior
Короткий ответ: display управляет тем, как элемент участвует в потоке и какой layout-контекст создаёт для детей.
Подробно:
block— занимает всю доступную ширину, начинается с новой строки. Принимаетwidth/height, вертикальныеmargin/padding. (div,p,section).inline— в строке, ширина по содержимому. Игнорируетwidth/heightи вертикальныеmargin; горизонтальныеpadding/marginприменяются, но не «раздвигают» строки корректно. (span,a,em).inline-block— в строке (как inline), но принимаетwidth/heightи все отступы (как block). Хорош для «кнопок в ряд».none— элемент удаляется из потока и не рендерится (в отличие отvisibility: hidden, которое скрывает, но место сохраняет).flex— одномерный layout-контекст (строка ИЛИ колонка) для детей.grid— двумерный layout-контекст (строки И колонки одновременно).
.inline-block-row span { display: inline-block; width: 100px; height: 40px; }
⚠️ Ловушка: у inline-block появляется «фантомный» пробел между элементами из-за переносов строк в HTML (whitespace интерпретируется как символ пробела). Лечится font-size: 0 у родителя, отрицательным margin или переходом на flex.
03Чем отличаются static, relative, absolute, fixed, sticky? Относительно чего позиционируется элемент?
middle
Короткий ответ: position определяет точку отсчёта для top/right/bottom/left и участие в потоке.
Подробно:
static(по умолчанию) — в обычном потоке, смещенияtop/leftигнорируются.relative— остаётся в потоке (место занимает на старом месте), но визуально смещается относительно самого себя. Создаёт точку отсчёта для absolute-детей.absolute— выпадает из потока, позиционируется относительно ближайшего предка сposition != static(а если такого нет — относительно<html>/viewport).fixed— выпадает из потока, позиционируется относительно viewport, не двигается при скролле.sticky— гибрид: ведёт себя какrelative, пока скролл не достигнет порога (top: 0), затем «прилипает» какfixedв пределах родителя.
.parent { position: relative; } /* точка отсчёта */
.child { position: absolute; top: 0; right: 0; } /* в правый верхний угол родителя */
.header { position: sticky; top: 0; } /* прилипает к верху при скролле */
⚠️ Ловушка: position: sticky не работает, если у родителя overflow: hidden/auto/scroll, либо у родителя нет высоты, либо не задан хотя бы один порог (top/bottom). fixed/absolute ломают свою привязку к viewport, если у предка задан transform, filter или will-change — такой предок становится containing block.
04Объясни Flexbox: главная/поперечная оси, justify-content, align-items, flex-grow/shrink/basis.
middle
Короткий ответ: Flexbox — одномерная раскладка. justify-content выравнивает по главной оси, align-items — по поперечной. flex (grow shrink basis) управляет тем, как элемент растёт/сжимается.
Подробно:
Главная ось задаётся flex-direction (row — горизонтальная, column — вертикальная). Поперечная — перпендикулярна ей.
.container {
display: flex;
flex-direction: row; /* главная ось — горизонталь */
justify-content: space-between; /* распределение по главной оси */
align-items: center; /* выравнивание по поперечной оси */
gap: 16px; /* промежутки */
}
justify-content:flex-start | center | flex-end | space-between | space-around | space-evenly.align-items:stretch (default) | center | flex-start | flex-end | baseline.flex-grow— коэффициент роста при наличии свободного места (0 = не растёт).flex-shrink— коэффициент сжатия при нехватке места (1 = может сжиматься).flex-basis— базовый размер до распределения (auto= по содержимому).
.item { flex: 1 1 0; } /* grow shrink basis — все элементы равной ширины */
.item { flex: 0 0 200px; } /* фиксированные 200px, не растёт, не сжимается */
Когда использовать: один ряд/колонка элементов — навбары, тулбары, выравнивание по центру, распределение кнопок.
⚠️ Ловушка: flex: 1 раскрывается в 1 1 0%, а не 1 1 auto — это разные вещи. С basis: 0 элементы делят пространство поровну, с basis: auto — пропорционально содержимому. Ещё: min-width: auto (по умолчанию) мешает flex-элементу сжаться меньше содержимого — лечится min-width: 0.
05Когда использовать Grid вместо Flexbox? Расскажи про template и areas.
middle
Короткий ответ: Grid — двумерная раскладка (ряды И колонки сразу). Используйте, когда нужна сетка/макет страницы; Flex — когда раскладка одномерная.
Подробно:
.grid {
display: grid;
grid-template-columns: 200px 1fr 1fr; /* fr — доля свободного места */
grid-template-rows: auto 1fr auto;
gap: 16px;
}
Грид-области (areas) дают наглядный макет:
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Полезные функции: repeat(3, 1fr), minmax(200px, 1fr), авто-адаптивная сетка без media queries:
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
⚠️ Ловушка: не противопоставляйте их жёстко — они дополняют друг друга: Grid для общего макета страницы, Flex для содержимого внутри ячеек. fr учитывает gap, а проценты — нет, поэтому 1fr надёжнее 33% для колонок.
06Как считается специфичность? Что делает !important и как работает каскад?
middle
Короткий ответ: Специфичность — вес селектора в виде кортежа (inline, id, class, tag). При равной специфичности побеждает правило, объявленное позже. !important перебивает обычную специфичность.
Подробно: Считаем по группам (a, b, c, d):
- a — inline-стиль (
style="...") = 1000 условных очков. - b — количество id селекторов (
#nav) = 100 каждый. - c — количество классов, атрибутов, псевдоклассов (
.btn,[type],:hover) = 10 каждый. - d — количество тегов и псевдоэлементов (
div,::before) = 1 каждый.
#nav .item a /* 0,1,1,1 = 111 */
.menu .item a /* 0,0,2,1 = 21 */
ul li a /* 0,0,0,3 = 3 */
/* первый победит */
* (универсальный) и комбинаторы (>, +, ~) не добавляют специфичности.
Каскад разрешает конфликты в порядке: важность (!important) → специфичность → порядок объявления.
⚠️ Ловушка: !important — последнее средство; войны !important-ов перебиваются только другим !important с большей специфичностью. Inline-стиль с !important практически непобедим (кроме !important в user-stylesheet). Современная альтернатива — @layer (каскадные слои) для управления приоритетом без специфичности.
07Какие бывают селекторы и комбинаторы? Чем псевдокласс отличается от псевдоэлемента?
junior
Короткий ответ: Комбинаторы: A B (потомок), A > B (прямой ребёнок), A + B (следующий соседний), A ~ B (все последующие соседи). Псевдокласс — состояние (:hover), псевдоэлемент — виртуальная часть (::before).
Подробно:
.list li { } /* потомок (любой уровень вложенности) */
.list > li { } /* только прямые дети */
h2 + p { } /* первый <p> сразу после <h2> */
h2 ~ p { } /* все <p> после <h2> на том же уровне */
Псевдоклассы (одно двоеточие) — состояния и позиция:
a:hover { }
input:focus { }
li:nth-child(2n) { } /* чётные */
li:first-child { }
li:not(.active) { }
Псевдоэлементы (два двоеточия) — генерируемое/частичное содержимое:
.quote::before { content: "«"; }
p::first-line { font-weight: bold; }
input::placeholder { color: gray; }
⚠️ Ловушка: :nth-child(n) считает среди ВСЕХ детей родителя, а :nth-of-type(n) — только среди элементов того же тега. ::before/::after не работают без свойства content (даже content: "") и не появляются у replaced-элементов (img, input).
08Какие свойства наследуются, а какие нет?
junior
Короткий ответ: Наследуются в основном свойства, связанные с текстом (color, font-*, line-height, text-align, visibility). НЕ наследуются margin, padding, border, width, background, display, position.
Подробно: Можно явно управлять наследованием:
.child { color: inherit; } /* взять у родителя */
.child { all: unset; } /* сбросить всё */
button { font: inherit; } /* кнопки не наследуют шрифт — частый фикс */
Значения inherit (наследовать), initial (значение по спецификации), unset (наследовать если свойство наследуемое, иначе initial), revert (откатить к стилю браузера).
⚠️ Ловушка: form-элементы (button, input, select, textarea) НЕ наследуют font-family/font-size от родителя по умолчанию — поэтому часто пишут button { font: inherit; }. Наследуется вычисленное значение, например line-height: 1.5 наследуется как множитель, а line-height: 24px — как фиксированное значение.
09Чем отличаются px, em, rem, %, vw, vh, fr? В чём разница em и rem?
junior
Короткий ответ: px — абсолютные пиксели; em — относительно font-size текущего элемента; rem — относительно font-size корня (<html>); % — относительно родителя; vw/vh — % от ширины/высоты viewport; fr — доля свободного места в grid.
Подробно:
html { font-size: 16px; }
.box { font-size: 2rem; } /* 32px — от корня */
.box { padding: 1em; } /* 32px — от font-size самого .box (=32) */
.full { width: 100vw; height: 100vh; } /* весь экран */
emкаскадирует/умножается: вложенные элементы сfont-size: 1.5emмножат размеры по цепочке → легко получить непредсказуемый результат.remвсегда привязан к одному источнику (корень) → предсказуем, поэтому предпочтителен для типографики и отступов.emудобен, когда нужно масштабировать что-то относительно собственного шрифта (например,paddingкнопки вemмасштабируется вместе с её текстом).
⚠️ Ловушка: 100vw включает ширину вертикального скроллбара → горизонтальный скролл на десктопе. Новые единицы dvh/svh/lvh (dynamic/small/large viewport height) решают проблему «прыгающей» адресной строки на мобильных, где 100vh некорректен.
10Что такое stacking context и почему z-index «не работает»?
senior
Короткий ответ: z-index действует только внутри одного контекста наложения. Элемент с высоким z-index внутри контекста с низким z-index всё равно окажется ниже соседнего контекста.
Подробно: Stacking context создаётся, в частности:
- корневым
<html>; - элементом с
position != staticИ заданнымz-index(неauto); opacity < 1,transform,filter,will-change,mix-blend-mode,isolation: isolate;- flex/grid-ребёнком с
z-index != auto.
.modal-parent { opacity: 0.99; } /* создаёт новый контекст! */
.modal { position: fixed; z-index: 9999; }
/* модалка с z-index 9999 окажется ПОД соседним блоком,
потому что заперта внутри контекста .modal-parent */
Внутри контекста порядок: фон родителя → отрицательные z-index → block-поток → float → inline → z-index:auto/0 → положительные z-index.
⚠️ Ловушка: классическая боль — z-index: 999999 не помогает, потому что родитель с transform или opacity создал изолированный контекст. Решение: вынести элемент выше по дереву (portal) или убрать свойство, создающее контекст. Используйте isolation: isolate, чтобы намеренно создать контекст и не давать z-index «протекать».
11Что такое margin collapse (схлопывание отступов)?
middle
Короткий ответ: Вертикальные margin соседних блоков и родитель/первый-ребёнок схлопываются в один — берётся наибольший, а не сумма.
Подробно: Три случая:
- Соседние блоки:
margin-bottom: 20px+margin-top: 30px= промежуток30px(не 50). - Родитель и первый/последний ребёнок:
margin-topребёнка «вытекает» наружу родителя. - Пустой блок: его собственные top и bottom схлопываются.
.a { margin-bottom: 20px; }
.b { margin-top: 30px; }
/* фактический отступ между .a и .b = 30px */
Как предотвратить схлопывание родителя с ребёнком: добавить родителю padding, border, overflow: hidden, или сделать его flex/grid-контейнером (в них схлопывания нет).
⚠️ Ловушка: схлопываются только вертикальные margin в нормальном потоке. Горизонтальные — никогда. В flex/grid контейнерах схлопывания margin нет вообще — частая причина «почему отступы вдруг изменились» при переводе на flex.
12Что такое float и зачем нужен clearfix?
middle
Короткий ответ: float исторически использовался для обтекания текстом картинок и для колоночных макетов. clearfix заставляет родителя «увидеть» высоту своих float-детей, которые выпадают из потока.
Подробно:
img { float: left; margin-right: 1em; } /* текст обтекает картинку */
Проблема: float-элементы не занимают высоту в родителе → родитель «схлопывается» в нулевую высоту. Решение — clearfix:
.clearfix::after {
content: "";
display: block;
clear: both;
}
Современная замена обоих кейсов макета — Flexbox/Grid. float остался только для обтекания текстом.
⚠️ Ловушка: clear: both сбрасывает обтекание И слева, и справа. Сегодня для макетов float не используют — но на собеседовании про clearfix спрашивают как про «легаси, которое надо понимать». Альтернатива clearfix без псевдоэлемента — overflow: auto/display: flow-root у родителя.
13Как делать адаптивность? Что такое mobile-first и чем responsive отличается от adaptive?
middle
Короткий ответ: Media queries меняют стили по ширине экрана. Mobile-first — пишем базовые стили под мобильный, расширяем через min-width. Responsive — плавная резина; adaptive — несколько фиксированных макетов под брейкпоинты.
Подробно:
/* mobile-first: база — мобильный, расширяем вверх */
.col { width: 100%; }
@media (min-width: 768px) { .col { width: 50%; } } /* планшет+ */
@media (min-width: 1024px) { .col { width: 33.33%; } } /* десктоп+ */
- Responsive — единый макет, плавно тянется (
%,fr,clamp(),minmax). - Adaptive — несколько заранее свёрстанных вариантов, переключаемых на брейкпоинтах.
Полезно: clamp(min, preferred, max) для «текучей» типографики без media queries:
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }
Не забыть viewport-метатег:
<meta name="viewport" content="width=device-width, initial-scale=1">
⚠️ Ловушка: mobile-first использует min-width (стили добавляются вверх), desktop-first — max-width. Смешивать опасно: перекрытия дают неожиданные результаты. Container queries (@container) — новый подход: стиль зависит от ширины контейнера, а не viewport.
14Что такое CSS-переменные (custom properties)?
junior
Короткий ответ: Это пользовательские свойства --name, читаемые через var(). В отличие от переменных препроцессоров, они живут в рантайме, каскадируют и наследуются.
Подробно:
:root {
--primary: #3b82f6;
--gap: 16px;
}
.btn { background: var(--primary); padding: var(--gap); }
.btn-danger { --primary: #ef4444; } /* переопределение в области видимости */
Можно менять из JS — отлично для тем:
document.documentElement.style.setProperty('--primary', '#10b981');
var() поддерживает фолбэк: var(--x, 8px).
⚠️ Ловушка: в отличие от Sass-переменных (компилируются и исчезают), CSS-переменные динамические и каскадируют — это плюс для тем, но var() нельзя использовать в media-query условиях (@media (min-width: var(--bp)) не работает). Имена регистрозависимы: --Primary ≠ --primary.
15transition vs animation/keyframes? Какие свойства анимировать дёшево?
middle
Короткий ответ: transition анимирует изменение между двумя состояниями (требует триггера, например :hover). animation + @keyframes — самостоятельная многошаговая анимация с циклами. Дёшево анимировать transform и opacity; дорого — свойства, триггерящие layout (width, top, margin).
Подробно:
/* transition: A -> B по событию */
.btn { transition: transform 0.2s ease; }
.btn:hover { transform: scale(1.1); }
/* animation: самостоятельная, может зациклиться */
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.dot { animation: pulse 1s infinite; }
Почему transform/opacity дёшевы: их умеет анимировать compositor на GPU — без пересчёта layout и без перерисовки слоёв. width, height, top, left, margin вызывают reflow на каждом кадре → дёргается весь экран.
⚠️ Ловушка: анимация box-shadow, filter, background вызывает repaint каждый кадр — дорого. Сместить элемент лучше через transform: translateX(), а не left. will-change: transform подсказывает браузеру создать слой заранее, но злоупотребление им жрёт память.
16Как отцентрировать элемент? Назови несколько способов.
junior
Короткий ответ: Flexbox (justify-content + align-items), Grid (place-items: center), position + transform, margin: auto.
Подробно:
/* 1. Flexbox — самый частый */
.parent { display: flex; justify-content: center; align-items: center; }
/* 2. Grid — короче всего */
.parent { display: grid; place-items: center; }
/* 3. absolute + transform — без знания размеров */
.child {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* 4. margin: auto в flex */
.child { margin: auto; }
/* 5. горизонтальное центрирование блока известной ширины */
.child { width: 300px; margin: 0 auto; }
⚠️ Ловушка: margin: 0 auto центрирует только по горизонтали и только блочные элементы с заданной шириной; вертикально так не получится (в обычном потоке). transform: translate(-50%, -50%) может давать размытие текста на нецелых пикселях — иногда добавляют will-change или округляют.
17Кратко: БЭМ, CSS-in-JS, CSS Modules, Tailwind — в чём суть?
middle
Короткий ответ: Это разные подходы к организации стилей и борьбе с глобальностью CSS.
Подробно:
- БЭМ (Block-Element-Modifier) — соглашение об именовании классов:
.card__title--active. Решает конфликты имён дисциплиной, без инструментов. Плоская специфичность (всё классы). - CSS Modules —
import styles from './x.module.css', имена хешируются в уникальные (Card_title_a3f9) → локальная область видимости, нет коллизий. Билд-тайм. - CSS-in-JS (styled-components, Emotion) — стили в JS, динамические по пропсам. Гибко, но рантайм-оверхед (часть мигрирует на zero-runtime).
- Tailwind — utility-first: классы-атомы (
flex p-4 text-lg) прямо в разметке. Нет нейминга, маленький итоговый CSS (purge неиспользуемого), но «шумная» разметка.
<!-- БЭМ --> <div class="card card--featured"><h2 class="card__title">…</h2></div>
<!-- Tailwind --> <div class="rounded-lg p-4 shadow"><h2 class="text-xl font-bold">…</h2></div>
⚠️ Ловушка: у всех общая цель — изоляция и предсказуемость стилей в большом проекте. Не существует «правильного» — выбор зависит от команды/стека. CSS-in-JS даёт runtime-стоимость; Tailwind требует привыкания и хорош с компонентами, чтобы не дублировать классы.
18Опиши конвейер рендеринга страницы браузером.
senior
Короткий ответ: HTML → DOM, CSS → CSSOM, их объединение → Render Tree, затем Layout (reflow) → Paint → Composite.
Подробно:
- Парсинг HTML → DOM — токенайзер строит дерево узлов. Встретив
<script>безasync/defer, парсер останавливается. - Парсинг CSS → CSSOM — дерево стилей. CSS render-blocking: рендер ждёт построения CSSOM.
- Render Tree — слияние DOM и CSSOM, только видимые узлы (без
display: none, ноvisibility: hiddenостаётся). - Layout (reflow) — вычисление геометрии: координаты и размеры каждого узла. Зависит от viewport.
- Paint — заливка пикселей по слоям (текст, цвета, тени, границы) в bitmap.
- Composite — сборка слоёв в финальную картинку, часто на GPU (учитывает
transform,opacity, z-порядок).
HTML ─► DOM ─┐
├─► Render Tree ─► Layout ─► Paint ─► Composite ─► экран
CSS ─► CSSOM┘
⚠️ Ловушка: display: none исключает узел из render tree (нет layout/paint), а visibility: hidden/opacity: 0 оставляют его в layout (место занято). JS может читать стили посреди этого конвейера и форсировать синхронный reflow.
19Reflow vs repaint vs composite — что дороже и что их триггерит?
senior
Короткий ответ: Reflow (layout) — пересчёт геометрии, самый дорогой. Repaint — перерисовка пикселей без изменения геометрии, дешевле. Composite — пересборка слоёв на GPU, самый дешёвый.
Подробно:
| Этап | Что меняется | Триггеры | Стоимость |
|---|---|---|---|
| Reflow/Layout | размеры/позиции | width, height, top/left, margin, font-size, добавление DOM-узлов, чтение offsetWidth |
🔴 высокая |
| Repaint | цвета/видимое | color, background, box-shadow, visibility |
🟡 средняя |
| Composite | только слой | transform, opacity (на отдельном слое) |
🟢 низкая |
Reflow тянет за собой repaint и composite (каскад). Поэтому минимизируют именно reflow.
Как минимизировать:
- Анимировать
transform/opacity, а неtop/width. - Батчить DOM-изменения, избегать layout thrashing (чередования записи и чтения геометрии):
// ПЛОХО: forced synchronous layout в цикле
for (const el of items) {
el.style.height = el.offsetHeight + 10 + 'px'; // чтение → запись → чтение…
}
// ХОРОШО: сначала все чтения, потом все записи
const hs = items.map(el => el.offsetHeight);
items.forEach((el, i) => el.style.height = hs[i] + 10 + 'px');
⚠️ Ловушка: чтение offsetTop/offsetWidth/getBoundingClientRect()/getComputedStyle() сразу после изменения стиля форсирует синхронный reflow — браузер вынужден немедленно пересчитать layout. Это «layout thrashing» и главный источник тормозов в скриптах с анимацией.
20Что такое critical rendering path и render-blocking ресурсы? Почему CSS в head, а JS — внизу/async/defer?
senior
Короткий ответ: Critical Rendering Path — последовательность шагов от получения HTML до первого пикселя. CSS блокирует рендеринг, синхронный JS блокирует и парсинг, и рендеринг. Поэтому CSS грузят рано (в <head>), а скрипты — не блокирующе.
Подробно:
- CSS render-blocking: браузер не отрисует контент, пока не построит CSSOM (иначе был бы FOUC — мелькание нестилизованного контента). Значит, CSS нужен как можно раньше и минимальным (см. Critical CSS).
- Синхронный
<script>parser-blocking: встретив его, парсер останавливает построение DOM, скачивает и выполняет скрипт (а скрипт ещё ждёт CSSOM, если читает стили). Поэтому исторически клали скрипты перед</body>.
<head>
<link rel="stylesheet" href="styles.css"> <!-- грузим стили рано -->
</head>
<body>
…контент…
<script src="app.js" defer></script> <!-- не блокирует парсинг -->
</body>
Цель — сократить путь: меньше критических ресурсов, меньше их объём, меньше «прыжков» туда-обратно по сети.
⚠️ Ловушка: CSS блокирует не только paint, но и выполнение JS, который идёт после него (браузер боится, что скрипт прочитает ещё не готовые стили). Поэтому огромный CSS тормозит и JS. @import внутри CSS добавляет лишний последовательный запрос — лучше <link>.
21В чём разница между async и defer у `<script>`?
middle
Короткий ответ: Оба грузят скрипт параллельно с парсингом HTML (не блокируют его при загрузке). defer выполняет скрипты после парсинга DOM и в порядке объявления; async выполняет сразу по загрузке, в произвольном порядке, прерывая парсинг.
Подробно:
<script src="a.js"></script> <!-- блокирует парсинг: download + execute -->
<script src="a.js" async></script> <!-- download ∥ parse, execute ASAP -->
<script src="a.js" defer></script> <!-- download ∥ parse, execute после DOMContentLoaded-этапа, по порядку -->
| Когда грузится | Когда выполняется | Порядок | |
|---|---|---|---|
| обычный | блокирует парсинг | сразу | по порядку |
async |
параллельно | как только скачан (может прервать парсинг) | НЕ гарантирован |
defer |
параллельно | после полного парсинга DOM | по порядку |
defer— для скриптов, которым нужен готовый DOM и важна последовательность (большинство app-кода).async— для независимых скриптов (аналитика, реклама), которым всё равно когда и в каком порядке.
⚠️ Ловушка: async/defer игнорируются у inline-скриптов (без src). Скрипты типа module ведут себя как defer по умолчанию. async-скрипт может выполниться раньше, чем DOM готов, — нельзя полагаться на наличие элементов.
22Что такое Core Web Vitals (LCP, INP/FID, CLS) и метрики TTFB, FCP?
middle
Короткий ответ: Core Web Vitals — три ключевые метрики Google: LCP (скорость загрузки), INP/FID (отзывчивость), CLS (визуальная стабильность). TTFB и FCP — вспомогательные метрики времени.
Подробно:
- TTFB (Time To First Byte) — время до первого байта ответа сервера. Влияет на всё последующее.
- FCP (First Contentful Paint) — первый отрисованный контент (текст/изображение).
- LCP (Largest Contentful Paint) — отрисовка крупнейшего видимого элемента (главный hero/картинка). Цель: < 2.5 с.
- FID (First Input Delay) — задержка реакции на первый ввод. Заменён на INP (Interaction to Next Paint) — отзывчивость на протяжении всей сессии. Цель INP: < 200 мс.
- CLS (Cumulative Layout Shift) — суммарный сдвиг макета (когда контент «прыгает»). Цель: < 0.1.
Как улучшать:
- LCP — preload hero-изображения/шрифта, быстрый сервер/CDN, оптимизация критического CSS.
- INP — дробить долгие JS-задачи,
requestIdleCallback, web workers. - CLS — задавать
width/heightуimg, резервировать место под рекламу/баннеры,font-display: optional.
<img src="hero.jpg" width="1200" height="600" alt="…"> <!-- резерв места против CLS -->
<link rel="preload" as="image" href="hero.jpg"> <!-- ускоряет LCP -->
⚠️ Ловушка: CLS чаще всего ломают изображения без width/height, динамически вставляемые баннеры и шрифты, меняющие метрики при подгрузке. FID измеряет только первое взаимодействие — INP честнее, потому что учитывает всю сессию.
23Какие есть способы оптимизации загрузки страницы?
middle
Короткий ответ: Уменьшать объём (минификация, сжатие, code splitting), грузить отложенно (lazy loading, defer), кэшировать (HTTP cache, CDN), предзагружать критичное (preload/prefetch).
Подробно:
- Минификация — удаление пробелов/комментариев из JS/CSS/HTML.
- Сжатие на транспорте —
gzip/brotli(br эффективнее). - Lazy loading изображений и iframe:
<img loading="lazy">; для компонентов — динамический импорт. - Code splitting — разбить бандл по роутам/фичам, грузить по требованию (
import()), tree-shaking убирает мёртвый код. - Кэширование —
Cache-Control, хешированные имена файлов (app.a3f9.js) для долгого кэша + мгновенной инвалидации. - CDN — раздача статики с ближайшего к пользователю узла (меньше latency).
- Resource hints:
preload(нужно сейчас, высокий приоритет),prefetch(понадобится позже),preconnect/dns-prefetch(заранее установить соединение).
<link rel="preload" as="font" href="font.woff2" crossorigin> <!-- критичный шрифт -->
<link rel="prefetch" href="/next-page.js"> <!-- следующий роут -->
<link rel="preconnect" href="https://api.example.com"> <!-- заранее открыть соединение -->
<script type="module" src="main.js"></script> <!-- современный бандл -->
Дерево зависимостей бандла — анализатор (webpack-bundle-analyzer) показывает, что раздуло сборку → удаляем тяжёлые/дублирующиеся либы, переходим на легковесные альтернативы.
⚠️ Ловушка: не злоупотреблять preload — слишком много высокоприоритетных запросов конкурируют за пропускную способность и тормозят критичные. lazy нельзя ставить на LCP-изображение (hero) — это замедлит LCP. Tree-shaking не работает на CommonJS-модулях и при сайд-эффектах.
24Что такое Critical CSS и проблемы шрифтов FOUT/FOIT?
senior
Короткий ответ: Critical CSS — минимальный набор стилей для контента «над сгибом», инлайнится в <head> для быстрого первого рендера. FOUT (Flash of Unstyled Text) — текст системным шрифтом до загрузки кастомного; FOIT (Flash of Invisible Text) — невидимый текст, пока шрифт грузится.
Подробно:
Critical CSS: выделяем стили видимой части, инлайним их, остальной CSS грузим асинхронно — чтобы первый paint не ждал большого таблицы стилей:
<head>
<style>/* critical: hero, header, шрифты */</style>
<link rel="preload" href="full.css" as="style" onload="this.rel='stylesheet'">
</head>
Шрифты управляются font-display:
swap— сразу показать fallback, потом подменить (FOUT) — текст всегда виден.block— короткая невидимость, ждём шрифт (FOIT).optional— если шрифт не успел, используем системный и не дёргаем layout (лучший для CLS).
@font-face {
font-family: 'Inter';
src: url('inter.woff2') format('woff2');
font-display: swap;
}
⚠️ Ловушка: FOUT даёт сдвиг макета (CLS), если у кастомного и fallback-шрифта разные метрики — лечится size-adjust/ascent-override или font-display: optional. Используйте woff2 (лучшее сжатие), подгружайте только нужные начертания, preload для критичных шрифтов.
25Что такое DOM? Почему операции с ним дорогие и что такое DocumentFragment?
middle
Короткий ответ: DOM (Document Object Model) — древовидное объектное представление HTML, через которое JS читает и меняет страницу. Изменения DOM дороги, потому что могут триггерить reflow/repaint. DocumentFragment — лёгкий offscreen-контейнер для батч-вставки.
Подробно: Каждое изменение DOM может заставить браузер пересчитать layout и перерисовать. Вставка узлов по одному в цикле = много reflow.
// ПЛОХО: 1000 вставок в живой DOM → потенциально 1000 reflow
for (let i = 0; i < 1000; i++) {
list.appendChild(document.createElement('li'));
}
// ХОРОШО: собираем во fragment, вставляем разом → 1 reflow
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
frag.appendChild(document.createElement('li'));
}
list.appendChild(frag);
Дорогие операции: чтение геометрии (offsetWidth, getBoundingClientRect), частые вставки/удаления, изменения, влияющие на размеры.
⚠️ Ловушка: innerHTML += в цикле — антипаттерн: каждый раз перепарсивается и пересобирается весь HTML, теряются обработчики событий. Лучше собрать строку и присвоить один раз, либо использовать fragment. Виртуальный DOM (React) существует именно чтобы минимизировать реальные DOM-операции через диффинг и батчинг.
26Объясни event delegation, bubbling/capturing, preventDefault/stopPropagation.
middle
Короткий ответ: События проходят три фазы: capture (сверху вниз), target, bubbling (снизу вверх). Делегирование — один обработчик на родителе ловит события всплывающих детей. preventDefault() отменяет действие браузера по умолчанию; stopPropagation() останавливает дальнейшее распространение.
Подробно:
// Делегирование: один слушатель вместо сотни
document.querySelector('#list').addEventListener('click', (e) => {
const li = e.target.closest('li');
if (li) handle(li.dataset.id);
});
- Capturing — событие идёт от
documentвниз к target (addEventListener(type, fn, true)). - Target — достигает целевого элемента.
- Bubbling — всплывает обратно вверх (по умолчанию слушатели срабатывают здесь).
e.preventDefault(); // отменить дефолт (переход по ссылке, отправку формы)
e.stopPropagation(); // не пускать событие дальше по дереву
e.stopImmediatePropagation(); // + не вызывать другие слушатели на этом же элементе
⚠️ Ловушка: preventDefault() и stopPropagation() — разные вещи: первый не останавливает всплытие, второй не отменяет дефолтное действие. Не все события всплывают (focus, blur, mouseenter — не всплывают; есть всплывающие аналоги focusin, mouseover). Делегирование не сработает для неподнимающихся событий.
28Что важно знать про доступность (a11y)?
junior
Короткий ответ: Семантический HTML, текстовые альтернативы (alt), ARIA-атрибуты там, где семантики не хватает, и достаточный цветовой контраст.
Подробно:
- Семантика:
<button>,<nav>,<header>,<main>,<h1>…<h6>дают структуру скринридерам и клавиатурную навигацию бесплатно. Не делать кнопку из<div onclick>. altу изображений: описание для незрячих; пустойalt=""для декоративных.- ARIA:
aria-label,role,aria-expanded,aria-live— когда нативной семантики недостаточно (кастомные виджеты). - Контраст: текст к фону ≥ 4.5:1 (WCAG AA), крупный — ≥ 3:1.
- Клавиатура: всё кликабельное должно быть доступно с Tab/Enter; видимый
:focus.
<button aria-label="Закрыть" aria-expanded="false">✕</button>
<img src="logo.png" alt="Логотип компании">
<img src="divider.png" alt=""> <!-- декоративное -->
⚠️ Ловушка: «первое правило ARIA — не использовать ARIA, если есть нативный элемент». aria-label на неинтерактивном элементе бесполезен. Убирать outline с :focus без замены — частая ошибка доступности; используйте :focus-visible. Плейсхолдер не заменяет <label>.
29Что происходит, когда ты вводишь URL и жмёшь Enter? (фокус на рендеринге)
concept
Короткий ответ: DNS-резолв → TCP/TLS-соединение → HTTP-запрос → ответ сервера → браузер парсит HTML в DOM, CSS в CSSOM, строит render tree, делает layout, paint, composite и выводит пиксели.
Подробно:
- DNS — доменное имя → IP-адрес (с кэшированием на разных уровнях).
- TCP + TLS — установка соединения и шифрования (для HTTPS handshake).
- HTTP-запрос → сервер отдаёт HTML (TTFB — время до первого байта).
- Парсинг HTML → DOM, попутно браузер «препарсером» находит и предзагружает ресурсы.
- CSS → CSSOM (render-blocking), JS выполняется (parser-blocking, если синхронный).
- Render Tree = DOM + CSSOM (видимые узлы).
- Layout (reflow) — геометрия; Paint — пиксели по слоям; Composite — сборка (часто GPU).
- FCP/LCP — пользователь видит контент; затем гидрация/интерактивность.
URL → DNS → TCP/TLS → HTTP → HTML
├─► DOM ─┐
│ ├─► Render Tree → Layout → Paint → Composite → пиксели
└─► CSS ─► CSSOM┘
⚠️ Ловушка: на собесе по фронту акцент делают именно на части «после получения HTML» (рендеринг), а не на сетевой. Упомяни render-blocking CSS, parser-blocking JS, critical rendering path и почему важны метрики (LCP/FCP).
30Почему манипуляции с DOM «дорогие»?
concept
Короткий ответ: Изменение DOM может запускать reflow (пересчёт геометрии всей страницы) и repaint, а чтение геометрии сразу после записи форсирует синхронный layout. Эти пересчёты — самая дорогая часть кадра.
Подробно: DOM сам по себе — просто дерево объектов, доступ к нему не медленный. Дорог побочный эффект: браузеру приходится заново считать layout и перерисовывать. Чем больше дерево и чем чаще чередуются чтения/записи, тем хуже.
// layout thrashing: запись → чтение → запись → чтение… каждый шаг = reflow
box.style.width = '100px';
console.log(box.offsetHeight); // форсирует reflow
box.style.height = '50px';
console.log(box.offsetTop); // ещё reflow
Способы удешевить: батчить изменения, использовать DocumentFragment, менять класс вместо множества инлайн-стилей, читать геометрию пачкой до записей, анимировать transform/opacity, применять requestAnimationFrame для синхронизации с кадром.
⚠️ Ловушка: именно из-за этого появились виртуальные DOM (React) и батчинг — они собирают изменения и применяют минимальный набор реальных операций за один проход, избегая лишних reflow.
31Почему анимировать transform дешевле, чем top/left?
concept
Короткий ответ: transform/opacity обрабатываются на этапе composite (часто на GPU) без пересчёта layout и перерисовки. top/left/width/margin меняют геометрию → запускают reflow + repaint на каждом кадре.
Подробно: Анимация идёт 60 раз в секунду. Если каждый кадр требует полного reflow всей страницы, браузер не успевает за 16.6 мс и кадры «дропаются» (дёргание).
/* ДОРОГО: каждый кадр = layout + paint + composite */
.bad { transition: left 0.3s; }
.bad:hover { left: 100px; }
/* ДЁШЕВО: только composite на GPU */
.good { transition: transform 0.3s; }
.good:hover { transform: translateX(100px); }
Браузер может вынести анимируемый по transform/opacity элемент на отдельный композитный слой и двигать его независимо от остального layout. will-change: transform подсказывает создать слой заранее.
⚠️ Ловушка: дешевизна работает, только пока элемент на собственном слое; чрезмерное число слоёв или will-change на всём подряд раздувает память GPU. transform влияет на визуал, но не на занимаемое в потоке место — другие элементы не сдвинутся.
32Зачем нужен event delegation?
concept
Короткий ответ: Чтобы вместо сотен обработчиков на каждом дочернем элементе повесить один на общего родителя, используя всплытие. Это экономит память и автоматически работает для динамически добавленных элементов.
Подробно:
// Без делегирования: N слушателей, и новые элементы их не получат
document.querySelectorAll('.item').forEach(el =>
el.addEventListener('click', handler)
);
// С делегированием: 1 слушатель, работает и для будущих .item
document.querySelector('#list').addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (item) handler(item);
});
Плюсы:
- Меньше слушателей → меньше памяти и быстрее навешивание.
- Динамически добавленные элементы обрабатываются без перенавешивания.
- Проще управлять (один источник истины).
⚠️ Ловушка: работает только для всплывающих событий; для focus/blur/mouseenter нужны невсплывающие исключения (берём focusin/mouseover). Обязательно проверять e.target.closest(...), иначе клик по вложенным элементам внутри .item может промахнуться. Нужно учитывать stopPropagation() внутри детей — он сломает делегирование.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.