100 популярных вопросов по Frontend-разработке

ВопросОтвет
Что такое цикл событий (Event Loop) и как он функционирует?Event Loop — это механизм в JavaScript‑рантаймах (браузер, Node.js), который обеспечивает выполнение асинхронного кода в однопоточном окружении. Он постоянно проверяет две основные очереди: макрозадач (macrotasks) и микрозадач (microtasks). Работает по следующему алгоритму: 1) Выполняет синхронный код, помещая функции в стек вызовов (call stack). 2) После опустошения стека проверяет очередь микрозадач (Promise‑handlers, MutationObserver и т.п.) и последовательно исполняет их, пока очередь не станет пустой. 3) Затем, если прошло достаточно времени и нет более приоритетных задач, обрабатывает одну макрозадачу из очереди (setTimeout, setInterval, I/O, UI‑события). 4) После каждой макрозадачи снова обрабатывает все накопившиеся микрозадачи. 5) На этапах рендеринга (reflow/repaint) браузер может выполнять обновление UI, если очередь пуста. В Node.js добавляются очереди фаз (timers, I/O callbacks, poll, check, close) и процесс.nextTick считается микрозадачей с самым высоким приоритетом. Event Loop гарантирует, что асинхронные операции не блокируют основной поток, но порядок их выполнения определяется приоритетом микрозадач над макрозадачами и фазой в Node.js.
Что представляет собой замыкание в JavaScript?Замыкание — это функция вместе со своей внешней (лексической) средой, в которой она была объявлена. При создании функции JavaScript сохраняет ссылки на все переменные из окружающих областей видимости, которые использует эта функция. Даже после того как внешняя функция завершит выполнение, вложенная функция продолжает иметь доступ к этим переменным, потому что их значения хранятся в специальном объекте «замкнутого» окружения. Замыкания позволяют реализовать инкапсуляцию, создавать функции‑фабрики, частичные применения, мемоизацию и поддерживать приватные данные. Пример:

```js
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter(); // counter – замыкание
console.log(counter()); // 1
console.log(counter()); // 2
```

В этом примере внутренняя анонимная функция сохраняет доступ к переменной `count`, хотя `makeCounter` уже завершил работу. Важно помнить, что замыкания удерживают ссылки на переменные, поэтому они могут влиять на сборку мусора и приводить к утечкам памяти, если не освобождать их явно. Также следует учитывать, что каждый вызов функции‑фабрики создаёт новое лексическое окружение, поэтому состояния разных замыканий изолированы друг от друга.
Что представляет собой Promise в JavaScript и как он используется?Promise — это объект, представляющий отложенную (асинхронную) операцию и её окончательный результат. Он может находиться в одном из трёх состояний: **pending** (ожидание), **fulfilled** (успешное завершение) или **rejected** (ошибка). При создании Promise принимает функцию‑исполнитель, получающую два колбэка — `resolve` и `reject`. Внутри этой функции размещают асинхронный код; при успешном завершении вызывают `resolve(value)`, а при ошибке — `reject(error)`.

Методы `then(onFulfilled, onRejected)` и `catch(onRejected)` позволяют подписаться на результат. Каждый вызов `then` возвращает новый Promise, что делает возможным цепочку (chaining) асинхронных операций, где результат одного шага передаётся следующему. Ошибки, возникшие в любой части цепочки, «прокидываются» к ближайшему обработчику `catch`.

Статические методы `Promise.resolve(value)`, `Promise.reject(reason)`, `Promise.all(iterable)`, `Promise.race(iterable)`, `Promise.allSettled(iterable)` предоставляют вспомогательные возможности: создание уже выполненного/отклонённого промиса, ожидание завершения всех промисов в массиве, получение первого завершившегося и т.д.

В современном JavaScript промисы часто используются вместе с синтаксисом `async/await`: функция, объявленная как `async`, автоматически возвращает промис, а оператор `await` приостанавливает её выполнение до разрешения переданного промиса, упрощая чтение кода.

Пример:

```js
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => xhr.status === 200 ? resolve(JSON.parse(xhr.responseText))
: reject(new Error(xhr.statusText));
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
});
}

fetchData('/api/data')
.then(data => {
console.log('Получены данные:', data);
return processData(data);
})
.then(result => console.log('Результат обработки:', result))
.catch(err => console.error('Ошибка:', err));
```

В этом примере `fetchData` возвращает промис, который затем обрабатывается цепочкой `then`/`catch`.

Таким образом, Promise — это фундаментальный механизм для управления асинхронными задачами в JavaScript, обеспечивающий предсказуемое и удобное управление состояниями и ошибками.
Что представляет собой Virtual DOM?Virtual DOM — это абстракция над реальным DOM, представляющая дерево UI‑элементов в памяти в виде обычных JavaScript‑объектов. При изменении состояния компонента фреймворк (React, Vue, Preact и др.) сначала обновляет эту виртуальную структуру, а затем сравнивает её с предыдущей версией (процесс reconciliation). На основе различий вычисляется минимальный набор операций (вставка, удаление, изменение атрибутов) и только они применяются к реальному DOM через батчинг и оптимизации, что значительно снижает количество дорогостоящих репейнтов и ре‑флоу. Virtual DOM позволяет писать декларативный код, где разработчик описывает, как UI должен выглядеть при определённом состоянии, а фреймворк сам управляет эффективным обновлением. Он также упрощает кросс‑браузерную совместимость, обеспечивает предсказуемое обновление и облегчает сервер‑сайд рендеринг, поскольку виртуальное дерево можно сериализовать. Однако Virtual DOM не избавляет от всех проблем производительности: при больших деревьях или частых обновлениях могут возникать затраты на сравнение (O(n)) и нужно использовать memoization, shouldComponentUpdate, PureComponent, useMemo и т.п. В некоторых случаях альтернативы (incremental DOM, прямой манипулятор) могут быть эффективнее.
Какие типы данных поддерживает JavaScript?В JavaScript различают примитивные типы — Number (включает NaN и Infinity), String, Boolean, Symbol, BigInt, undefined и null — а также тип Object, который охватывает функции, массивы, даты, регулярные выражения, карты (Map, WeakMap), наборы (Set, WeakSet), Promise и другие встроенные объекты. Оператор typeof возвращает «number», «string», «boolean», «symbol», «bigint», «undefined», «object» (для null и объектов) или «function» (для функций). Примитивы передаются по значению, объекты — по ссылке; примитивы неизменяемы, объекты изменяемы. Кроме того, существуют специальные значения: NaN (не число), Infinity и -Infinity, а также глобальные объекты Math и JSON.
Чем отличаются способы объявления переменных var, let и const в JavaScript?`var` объявляет переменную с функцией или глобальной областью видимости, поднимается (hoisting) и инициализируется значением `undefined` до её фактического места в коде; переменную можно переопределять и переназначать. `let` вводит переменную с блочной областью видимости, также поднимается, но находится в «временной мёртвой зоне» (TDZ) до строки инициализации, поэтому доступ к ней до объявления приводит к ReferenceError; её значение можно менять, но переобъявить в той же области нельзя. `const` почти как `let` — блочная область видимости и TDZ, но переменную нельзя переназначать после инициализации; при этом объект, присвоенный константе, остаётся мутируемым (можно изменять свойства). Кроме того, `var`‑переменные становятся свойствами глобального объекта (`window` в браузере), тогда как `let`/`const` — нет.
Чем отличаются тип (type) и интерфейс (interface) в TypeScript?В TypeScript `interface` и `type` позволяют описывать форму объектов, но различаются по возможностям и поведению:
- **Интерфейсы** могут расширяться (`extends`), реализовываться классами (`implements`), поддерживают декларативное слияние (можно объявлять один и тот же интерфейс несколько раз, и определения объединятся), могут объявлять только объектные структуры и функции.
- **Типовые алиасы** (`type`) могут описывать не только объектные формы, но и примитивы, объединения, пересечения, кортежи, условные и отображённые типы. Они не поддерживают декларативное слияние и не могут быть реализованы классом, но могут использоваться в качестве алиасов для сложных комбинаций типов.
Практически, если нужен простой контракт для объектов и возможность расширения или слияния, предпочтительнее `interface`. Если требуется описать сложный тип (union, tuple, mapped type) или использовать примитивные типы, лучше использовать `type`.
Примеры:
```ts
interface Person { name: string; age: number; }
type PersonAlias = { name: string; age: number };

interface A { a: number; }
interface A { b: string; } // слияние → { a: number; b: string; }

type B = A | { c: boolean }; // union, недоступно для interface
```
Оба подхода совместимы с системой структурного типизации TypeScript, и часто их используют взаимозаменяемо, выбирая наиболее удобный синтаксис для конкретной задачи.
Что такое обобщения (Generics) в программировании?Generics — это механизм параметризации типов, позволяющий писать компоненты (функции, классы, интерфейсы) без указания конкретных типов данных, а затем подставлять эти типы при использовании. Благодаря Generics достигается статическая типовая безопасность, повторное использование кода и избежание приведения типов. В TypeScript, например, можно объявить функцию `function identity<T>(arg: T): T { return arg; }`, где `T` — плейсхолдер типа; при вызове `identity<string>('hello')` компилятор проверит, что аргумент и возвращаемое значение имеют тип `string`. Аналогичный подход существует в Flow, C# и Java. Generics также позволяют описывать сложные структуры данных (массива, карты, наборы) и гарантировать, что операции над ними работают только с согласованными типами, что упрощает рефакторинг и повышает читаемость кода.
Что представляет собой хук useCallback и в каких случаях его следует применять?useCallback — это React‑хук, который возвращает «запомненную» функцию, изменяющуюся только тогда, когда изменяются указанные зависимости. Он используется для оптимизации производительности, позволяя избежать лишних пересозданий функций при каждом рендере, что особенно важно при передаче колбэков в дочерние компоненты, мемоизированные с React.memo, или в зависимости от useEffect, useMemo и useImperativeHandle. Синтаксис: `const memoizedCallback = useCallback(() => { /* … */ }, [dep1, dep2]);`. При изменении любого из зависимостей массив‑зависимостей, React создаст новую функцию; иначе будет возвращена прежняя ссылка, что позволяет React‑мемоизации сравнивать пропсы по ссылке и не выполнять лишние обновления. useCallback часто сравнивают с useMemo: первый мемоизирует функции, второй — любые вычисляемые значения. При использовании в TypeScript тип функции указывается явно, а при работе с асинхронными колбэками следует помнить, что зависимости должны включать все используемые внешние переменные, иначе может возникнуть «запоминание» устаревших значений. Важно не переусердствовать: избыточное применение useCallback может увеличить сложность кода и даже ухудшить производительность из‑за дополнительных проверок, поэтому его используют только там, где действительно наблюдаются проблемы с лишними рендерами.
В чём различаются хуки useEffect и useLayoutEffect?useEffect вызывается после того, как React отрисовал компоненты и обновил DOM, то есть в фазе «commit» браузера, и не блокирует отображение. Он подходит для побочных эффектов, не влияющих на измерения layout (запросы к API, подписки, таймеры). useLayoutEffect выполняется синхронно сразу после изменения DOM, но до отрисовки браузером (внутри той же фазы commit). Поэтому он блокирует отображение, но гарантирует, что любые чтения/изменения layout (например, измерения размеров, скролл, позиционирование) произойдут до того, как пользователь увидит изменения. При этом useLayoutEffect может вызвать визуальные «запаздывания», если тяжёлый код, поэтому использовать его следует только когда нужны точные измерения или синхронные изменения layout. Оба хука поддерживают функцию очистки, вызываемую перед следующим вызовом эффекта или при размонтировании. В серверном рендеринге оба хука не вызываются, но React выдаёт предупреждение, если useLayoutEffect используется в среде без DOM. Кратко: useEffect — асинхронный, не блокирует рендер; useLayoutEffect — синхронный, блокирует рендер и используется для работы с layout.
Что такое React?React — это JavaScript‑библиотека для построения пользовательских интерфейсов, разработанная компанией Meta (Facebook). Она позволяет создавать UI в виде декларативных, переиспользуемых компонентов, управляя их состоянием и жизненным циклом. Основные принципы React: виртуальный DOM (Virtual DOM) для эффективного обновления реального DOM, однонаправленный поток данных (props), управление состоянием через useState, useReducer и контекст, а также мощные хуки (useEffect, useMemo и др.) для работы с побочными эффектами и оптимизаций. React поддерживает JSX — синтаксический сахар, позволяющий писать разметку внутри JavaScript, а также серверный рендеринг (ReactDOMServer), статическую генерацию (Next.js, Gatsby) и мобильные приложения (React Native). Экосистема включает роутинг (React Router), управление глобальным состоянием (Redux, MobX, Zustand), инструменты тестирования (Jest, React Testing Library) и множество UI‑библиотек (Material‑UI, Ant Design). React широко применяется в веб‑разработке для создания масштабируемых, отзывчивых и поддерживаемых приложений.
Для чего применяется хук useMemo?Хук useMemo служит для мемоизации результатов вычислений, позволяя кешировать значение, полученное в результате дорогостоящей функции, и переиспользовать его между рендерами, пока не изменятся указанные зависимости. Это помогает избежать повторных вычислений при каждом рендере компонента, тем самым улучшая производительность, особенно в случаях с большими массивами, сложными вычислениями или при передаче мемоизированных значений в дочерние компоненты, которые используют React.memo. Синтаксис: `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);`. При изменении любого из зависимых значений (`a` или `b`) функция будет выполнена заново, иначе будет возвращено ранее сохранённое значение. Важно помнить, что useMemo не гарантирует абсолютную экономию ресурсов, если вычисление дешево, и не следует использовать его для побочных эффектов – для этого предназначен useEffect.
Имеете ли вы опыт работы с TypeScript?TypeScript — это надмножество JavaScript, добавляющее статическую типизацию, интерфейсы, перечисления и другие возможности, которые позволяют писать более надёжный и поддерживаемый код. Чтобы начать работать с TypeScript, необходимо установить компилятор (`npm i -D typescript`) и создать файл `tsconfig.json`, в котором задаются параметры компиляции (целевой ECMAScript, модули, строгий режим и т.д.). После этого файлы с расширением `.ts` компилируются в обычный JavaScript командой `tsc` или через интеграцию в сборщики (Webpack, Rollup, Vite) с помощью `ts-loader` или `esbuild`.

Типичные задачи, решаемые с TypeScript, включают:
- Описание форм данных через типы и интерфейсы, что позволяет IDE подсвечивать ошибки на этапе разработки;
- Использование дженериков для создания переиспользуемых функций и компонентов;
- Интеграция с популярными фронтенд‑фреймворками (React, Angular, Vue) — в React часто применяют типизацию пропсов и хуков, в Angular TypeScript является обязательным;
- Улучшение процесса рефакторинга и масштабирования проекта за счёт строгой типовой проверки;
- Возможность писать декларационные файлы (`.d.ts`) для типизации сторонних библиотек без собственного TypeScript‑кода.

Для отладки удобно использовать source maps, которые позволяют видеть оригинальный TypeScript‑код в браузерных инструментах. При работе в командных проектах рекомендуется включать строгий режим (`"strict": true`) и линтеры (ESLint с `@typescript-eslint/parser`) для поддержания единого стиля кода.

Если вы уже использовали TypeScript, вы, вероятно, знакомы с такими аспектами, как настройка компиляции, работа с типами `any`/`unknown`, использование утилитных типов (`Pick`, `Partial`, `Record`), а также миграция существующего JavaScript‑проекта в TypeScript поэтапно.

Если опыта нет, начните с небольших скриптов, постепенно добавляя типы, и изучайте официальную документацию и практические руководства, чтобы понять, как TypeScript повышает качество кода в масштабных фронтенд‑приложениях.
В чём различаются cookie, localStorage и sessionStorage?Cookie — механизм хранения небольших данных (до ≈ 4 KB) на стороне клиента, который автоматически отправляется серверу в каждом HTTP‑запросе и может иметь указанные параметры — expires/max‑age, path, domain, Secure, HttpOnly, SameSite. Доступен как через заголовки HTTP, так и через JavaScript (document.cookie), но чтение/запись ограничены строковым форматом и требует парсинга.
localStorage — часть Web Storage API, предоставляет постоянное клиент‑стороннее хранилище (до ≈ 5‑10 MB в большинстве браузеров). Данные сохраняются в виде пар ключ‑значение (строки), не отправляются серверу, доступны только в контексте того же происхождения (origin) и сохраняются даже после закрытия вкладки/браузера.
sessionStorage — аналогично localStorage, но область видимости ограничена текущей вкладкой (или окном). Данные удаляются при закрытии вкладки/браузера. Как и localStorage, не передаются серверу и имеют аналогичный объём (≈ 5‑10 MB).
Ключевые различия: способ передачи (cookie — в запросах, storage — только клиент), время жизни (cookie — заданное время, sessionStorage — жизнь сессии, localStorage — постоянно), объём хранилища (cookie ≈ 4 KB, storage ≈ 5‑10 MB), доступность (cookie может быть HttpOnly, недоступен JavaScript, storage всегда доступен через JS), ограничения по пути/домену (cookie), а также поддержка безопасных атрибутов (Secure, SameSite) только у cookie.
В чём различаются стрелочные функции и обычные функции в JavaScript?Стрелочные функции (arrow functions) вводятся в ES6 и отличаются от обычных объявлений `function` следующими особенностями:
- **Не имеют собственного `this`**: значение `this` берётся из лексически окружающего контекста при их создании, что упрощает работу с методами классов и колбэками. Обычные функции формируют собственный `this`, зависящий от способа вызова (как метод, как конструктор, через `call/apply`, или в строгом режиме – `undefined`).
- **Отсутствие `arguments`**, `super` и `new.target`: в стрелочных функциях эти псевдо‑переменные недоступны; для доступа к аргументам используют остаточный параметр `...args`. Обычные функции имеют собственный объект `arguments` и могут использовать `super` в классах.
- **Нельзя использовать как конструктор**: попытка вызвать стрелочную функцию с `new` приводит к ошибке `TypeError`. Обычные функции могут быть конструктором, создавая новые объекты и имея свой прототип.
- **Не имеют собственного `prototype`**: у стрелочных функций нет свойства `prototype`, поэтому они не могут выступать в роли прототипных методов. Обычные функции автоматически получают `prototype`, позволяя наследовать свойства.
- **Различия в hoisting**: объявленные через `function` (function declaration) поднимаются (hoisted) и доступны до их места определения. Стрелочные функции обычно объявляются как выражения (`const fn = () => {}`) и не поднимаются.
- **Синтаксический сахар**: стрелочные функции позволяют опустить фигурные скобки и `return` при однострочном выражении, а также упрощают запись анонимных функций. Кроме того, они поддерживают короткую форму без параметров `() => {}` и с единственным параметром без скобок `x => x * 2`.
- **Поведение в классах**: при объявлении методов класса как стрелочных функций (через свойства класса) `this` фиксируется в момент создания экземпляра, что избавляет от необходимости привязывать методы вручную (`this.method = this.method.bind(this)`). Обычные методы класса используют динамический `this`.

В итоге, стрелочные функции удобны для коротких колбэков и сохранения контекста, тогда как обычные функции дают более гибкое управление `this`, могут выступать конструкторами и имеют свои собственные свойства `arguments` и `prototype`.
Опиши самый сложный фронтенд‑проект, над которым ты работал, указав его цели, используемые технологии и основные сложности.Самый сложный проект — это масштабная e‑commerce платформа с микрофронтенд‑архитектурой, обслуживающая миллионы пользователей в режиме реального времени. В качестве стека использовались React + TypeScript, Redux Toolkit для глобального состояния, GraphQL для оптимального получения данных, сервер‑сайд рендеринг (Next.js) для SEO и ускорения первого рендеринга, Webpack 5 с Module Federation для динамической загрузки микрофронтендов, а также SASS и Styled‑Components для стилизации. Основные задачи включали построение единой системы аутентификации и авторизации через OAuth2, обеспечение бесшовного перехода между микрофронтендами без перезагрузки, оптимизацию bundle size (code‑splitting, tree‑shaking, lazy loading) до < 200 KB, внедрение продвинутой кеш‑стратегии с Service Workers и Workbox для офлайн‑режима, обеспечение кросс‑браузерной совместимости (IE 11 + современные браузеры), реализацию полной типизации и автоматических проверок через ESLint и Prettier, покрытие кода юнит‑тестами (Jest + React Testing Library) и энд‑тоу‑энд тестами (Cypress), а также настройку CI/CD пайплайна в GitHub Actions с автоматическим деплоем в Kubernetes. Сложности возникали при синхронизации состояния между независимыми микрофронтендами, управлении версиями общих библиотек без конфликтов, отладке производительности на уровне первого контентногоful paint (FCP) и time‑to‑interactive (TTI), а также при координации работы распределённой команды из 12 разработчиков, дизайнеров и QA‑инженеров, требующей строгих code‑review процессов и документированных API контрактов.
Что подразумевается под принципами SOLID?SOLID — это набор из пяти основных принципов объектно‑ориентированного проектирования, направленных на создание гибких, расширяемых и поддерживаемых систем: S (Single Responsibility Principle) — класс должен иметь одну ответственность и один мотив изменения; O (Open/Closed Principle) — программные сущности должны быть открыты для расширения, но закрыты для модификации; L (Liskov Substitution Principle) — объекты подклассов должны без нарушения поведения заменять объекты базового класса; I (Interface Segregation Principle) — клиенты не должны зависеть от методов, которые они не используют, поэтому интерфейсы следует разбивать на более узкие; D (Dependency Inversion Principle) — модули верхних уровней не должны зависеть от модулей нижних, а обе группы должны зависеть от абстракций, а детали реализации должны зависеть от абстракций, а не наоборот. Применение SOLID упрощает рефакторинг, повышает тестируемость, облегчает повторное использование кода и снижает риск появления скрытых зависимостей, что особенно важно в больших проектах и при работе в команде.
Как определяется асинхронность и в чём её суть?Асинхронность — это способ выполнения кода, при котором операции, требующие длительного времени (сетевые запросы, чтение файлов, таймеры и т.п.), не блокируют основной поток исполнения, а запускаются в фоновом режиме; результат их выполнения обрабатывается позже через колбэки, промисы или async/await, а управление сразу возвращается к текущему стеку вызовов. В браузерах асинхронность реализована через событийный цикл: стек вызовов исполняет синхронный код, по завершении передаёт управление очереди задач (macrotasks) и микрозадачам (microtasks), где находятся обработчики промисов и задачи из setTimeout/setInterval. API — fetch, XMLHttpRequest, Web Workers, setTimeout, requestAnimationFrame, IndexedDB и др. – работают асинхронно. В JavaScript ключевые конструкции: функции‑колбэки, объект Promise (then, catch, finally), синтаксис async / await, который автоматически преобразует промис в «псевдо‑синхронный» код, позволяя писать последовательные операции без вложенности. Преимущества: отсутствие блокировки UI, возможность параллельной обработки запросов, улучшенная отзывчивость. Недостатки: сложность отладки, необходимость управления состоянием, возможные гонки и утечки памяти при неправильном использовании отменяемых запросов.
Что такое чистая функция в контексте программирования?Чистая функция — это функция, результаты работы которой полностью определяются только её входными аргументами и которая не имеет побочных эффектов. То есть при одинаковом наборе аргументов она всегда возвращает одно и то же значение и не изменяет внешнее состояние: не читает и не пишет глобальные переменные, не взаимодействует с файловой системой, сетью, UI и т.п. Такие функции являются детерминированными, их можно безопасно вызывать параллельно, кэшировать (memoization) и использовать в тестах без настройки окружения. Пример чистой функции в JavaScript:

```js
function sum(a, b) {
return a + b; // только зависимость от a и b, без побочных эффектов
}
```

Пример нечистой функции:

```js
let counter = 0;
function increment() {
counter += 1; // изменяет внешнюю переменную → побочный эффект
return counter;
}
```

Преимущества чистых функций: упрощённое отладка, предсказуемость, возможность применения оптимизаций компилятора, легкость рефакторинга и лучшая совместимость с концепциями функционального программирования.
Вы писали тесты для своего кода?Да, я регулярно пишу тесты для фронтенд‑приложений, используя комбинацию юнит‑тестов, интеграционных и энд‑ту‑энд тестов. Для юнит‑тестов обычно применяю Jest вместе с React Testing Library (или Vue Test Utils, если работаю с Vue), покрывая отдельные функции, компоненты и их логику, проверяя рендеринг, состояние и взаимодействия. Интеграционные тесты реализую через тот же Jest + Testing Library, проверяя взаимодействие нескольких компонентов и работу с контекстом, роутером и глобальным состоянием (Redux, Vuex). Энд‑ту‑энд тесты пишу на Cypress или Playwright, моделируя реальные пользовательские сценарии, такие как навигация, формы, авторизация и работа с API, включая моки и перехваты запросов. Я использую подходы TDD/BDD, пишу тесты до реализации функции, поддерживаю высокие показатели покрытия кода (80 %+), интегрирую тесты в CI/CD (GitHub Actions, GitLab CI), обеспечивая быстрый фидбек и автоматический запуск тестов при каждом пул‑реквесте. Кроме того, применяю мок‑серверы (MSW) для изоляции тестов от реального бекенда, а также слежу за качеством тестов с помощью линтеров и статических анализаторов.
Опиши самые захватывающие задачи, которые ты решал в области фронтенда.За последние годы я решал задачи, требующие сочетания архитектурных решений и глубокой оптимизации: разработал масштабируемое SPA на React с использованием Redux‑Toolkit и RTK Query, внедрил серверный рендеринг (Next.js) для снижения времени первой отрисовки и улучшения SEO; провёл детальный аудит производительности крупного интернет‑магазина, уменьшив Time‑to‑Interactive с 8 сек до 2,3 сек за счёт lazy‑loading, code‑splitting и оптимизации критического CSS; создал интерактивный визуализатор данных на Canvas/WebGL с кастомными шейдерами, обеспечив 60 fps даже при отображении 10 000 элементов; реализовал полную доступность (WCAG 2.1) для корпоративного портала, включая ARIA‑метки, управление фокусом и поддержку экранных читалок; построил CI/CD‑pipeline с автоматическим деплоем PWA‑приложения, интегрировав Lighthouse‑аудиты и автоматические регрессионные тесты UI.
Имеете ли вы практический опыт работы с Redux?Да, я использовал Redux в нескольких коммерческих и учебных проектах. В начале я создавал хранилище вручную, определяя отдельные редьюсеры, комбинировал их через `combineReducers`, описывал типизированные экшены и писал чистые функции‑редьюсеры. Для асинхронных операций применял middleware `redux-thunk` и `redux-saga`, организовывал цепочки эффектов, обрабатывал ошибки и отмену запросов. Позже перешёл на Redux Toolkit, где использовал `createSlice`, `createAsyncThunk` и встроенный `immer`‑базовый редьюсер, что упростило структуру кода и уменьшило бойлерплейт. Интеграция с React осуществлялась через `react-redux` (`Provider`, `useSelector`, `useDispatch`), применял мемоизацию селекторов с `reselect` для оптимизации ререндеров. Настраивал Redux DevTools для отладки, писал юнит‑тесты редьюсеров и саг с помощью `jest` и `redux-mock-store`. Также знаком с паттернами «feature‑sliced» и «ducks», использовал TypeScript для строгой типизации состояния и экшенов, а в больших приложениях внедрял код‑сплиттинг и динамическое добавление редьюсеров.
Что подразумевается под глубоким копированием объекта?Глубокое копирование (deep copy) — это процесс создания нового объекта, полностью независимого от исходного, при котором копируются все вложенные структуры данных: свойства‑примитивы, вложенные объекты, массивы, карты, наборы и т.д. В результате изменения в копии не влияют на оригинал и наоборот. В отличие от поверхностного (shallow) копирования, где копируются только ссылки на вложенные объекты, при глубоком копировании рекурсивно клонируются все уровни вложенности. В JavaScript для глубокого копирования часто используют: • `structuredClone(value)` — встроенная функция, поддерживающая большинство типов и умеющая обходить циклические ссылки; • `JSON.parse(JSON.stringify(obj))` — простой способ, но не сохраняет функции, `Date`, `Map`, `Set` и ломает прототип; • библиотеки типа `lodash.cloneDeep` — реализуют рекурсивное копирование с учётом специальных объектов; • ручная рекурсия с проверкой типа и обработкой циклических ссылок (WeakMap). При глубоком копировании важно учитывать: - классы и их методы (прототипы); - особые типы (`Date`, `RegExp`, `Map`, `Set`, `Blob` и пр.); - циклические ссылки, которые требуют отслеживания уже скопированных объектов; - производительность, так как рекурсивное обход всех узлов может быть дорогим для больших структур. Глубокое копирование применяется, когда необходимо безопасно изменять сложные состояния (например, в Redux, иммутабельных структурах, тестах), передавать данные между воркерами или сохранять состояние без риска непреднамеренных мутаций.
Какие utility‑типы предоставляет TypeScript?Utility‑типы в TypeScript — это предопределённые обобщённые типы, позволяющие трансформировать и комбинировать другие типы. Основные из них:

- **Partial<T>** – делает все свойства T необязательными.
```ts
type User = { id: number; name: string };
type PartialUser = Partial<User>; // { id?: number; name?: string }
```
- **Required<T>** – преобразует все свойства T в обязательные.
```ts
type Opt = { a?: number; b?: string };
type AllReq = Required<Opt>; // { a: number; b: string }
```
- **Readonly<T>** – делает свойства только для чтения.
```ts
type R = Readonly<User>; // { readonly id: number; readonly name: string }
```
- **Pick<T, K>** – выбирает подмножество свойств K из T.
```ts
type IdOnly = Pick<User, "id">; // { id: number }
```
- **Omit<T, K>** – исключает свойства K из T.
```ts
type NoName = Omit<User, "name">; // { id: number }
```
- **Record<K, T>** – создаёт тип объекта с ключами K и значениями типа T.
```ts
type Flags = Record<"a" | "b" | "c", boolean>; // { a: boolean; b: boolean; c: boolean }
```
- **Exclude<T, U>** – удаляет из объединения T типы, присущие U.
```ts
type Ex = Exclude<"a" | "b" | "c", "a" | "c">; // "b"
```
- **Extract<T, U>** – оставляет в T только те типы, которые также присутствуют в U.
```ts
type Ext = Extract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
```
- **NonNullable<T>** – исключает `null` и `undefined` из T.
```ts
type NN = NonNullable<string | null | undefined>; // string
```
- **ReturnType<T>** – получает тип возвращаемого значения функции T.
```ts
type R = ReturnType<() => number[]>; // number[]
```
- **Parameters<T>** – получает кортеж типов параметров функции T.
```ts
type P = Parameters<(a: string, b: number) => void>; // [string, number]
```
- **ThisParameterType<T>** и **ThisType<T>** – работают с типом `this` в функциях.
- **Uppercase<S>**, **Lowercase<S>**, **Capitalize<S>**, **Uncapitalize<S>** – манипулируют регистром строковых литералов.

Дополнительные утилиты: **Awaited<T>**, **InstanceType<T>**, **Required<T>**, **Mutable<T>** (в пользовательских библиотеках), а также новые в последних версиях: **ExactOptionalPropertyTypes**, **Brand**, **OmitNever**, и т.д. Они позволяют гибко строить типы, избегать дублирования кода и повышать безопасность типизации в проектах.
Имеете ли вы опыт работы с class‑компонентами в React?Классические (class‑) компоненты в React объявляются как классы, наследующие React.Component, и позволяют использовать внутреннее состояние (this.state), методы жизненного цикла (componentDidMount, componentDidUpdate, componentWillUnmount и др.), а также обработчики событий с привязкой контекста (this). В них свойства передаются через this.props. Для обновления состояния используют this.setState, который асинхронно объединяет новое состояние с текущим. При работе с class‑компонентами важно правильно привязывать методы (bind) в конструкторе или использовать стрелочные функции, чтобы сохранить контекст. Кроме того, следует учитывать, что с появлением хуков (useState, useEffect и пр.) функциональные компоненты стали предпочтительнее, однако классические компоненты всё ещё актуальны в существующих кодовых базах, при миграции, в библиотеках, требующих жизненного цикла, или когда нужен доступ к рефам через createRef. Опыт работы с class‑компонентами включает создание, управление состоянием, оптимизацию рендеринга (shouldComponentUpdate, PureComponent), использование контекста (static contextType) и взаимодействие с Redux/ MobX через connect.
Что означает термин «контекст» в сфере фронтенд‑разработки?Контекст в фронтенде — это совокупность условий, в которых исполняется код, включающая область видимости переменных, привязку this и текущую лексическую среду; в JavaScript различают глобальный контекст, контекст функции и контекст eval, каждый из которых создаёт объект ExecutionContext с VariableEnvironment, LexicalEnvironment и thisBinding, формируя цепочку областей видимости и стек вызовов, а также в CSS контекст определяется наследованием стилей от родительского элемента, а в React Context позволяет передавать данные через дерево компонентов без прокидывания props, что делает понимание контекста ключевым для работы с замыканиями, асинхронным кодом, методами bind/call/apply и управлением состоянием UI‑фреймворков.
В чём отличается объявление функции (Function Declaration) от функционального выражения (Function Expression)?Function Declaration – это синтаксис `function name(params) { … }`, который создаёт именованную функцию и поднимает её (hoisting) в начало текущей области видимости, поэтому её можно вызывать до места объявления. Function Expression – это создание функции как части выражения, например `const foo = function(params) { … }` или стрелочная функция `const foo = (params) => { … }`; она может быть анонимной или именованной, но сама переменная получает значение только в момент выполнения кода, поэтому вызов до инициализации приводит к ошибке. Основные различия: • hoisting: декларации поднимаются полностью, экспрессии – только объявление переменной (если `var`) или не поднимаются вовсе (let/const); • имя: декларация обязана иметь имя, экспрессия может быть без имени; • возможность использования в IIFE и как аргумент функции: экспрессии часто применяются для создания замыканий и самовызывающихся функций; • поведение в строгом режиме и при объявлении в блоках: функции‑выражения в блоках (let/const) сохраняют блочную область видимости, а декларации в блоках могут вести себя по‑разному в разных движках; • возможность переопределения: переменная, получающая Function Expression, может быть переназначена, тогда как имя функции‑декларации обычно неизменяемо в пределах своей области.
Чем отличаются значения null и undefined в JavaScript?null — это преднамеренно присвоенное «пустое» значение, указывающее, что переменная существует, но не содержит полезных данных; undefined — это значение по умолчанию, которое получает переменная, объявленная без инициализации, или результат обращения к несуществующему свойству/аргументу функции. По типу typeof null возвращает "object" (историческая ошибка), а typeof undefined — "undefined". При строгом сравнении (===) null и undefined не равны, но при нестрогом (==) они считаются равными. Оба значения приводятся к false в логическом контексте, однако в арифметических операциях null преобразуется в 0, а undefined — в NaN. Присваивание null обычно используется разработчиком для «очистки» ссылки, тогда как undefined сигнализирует о том, что значение ещё не было задано системой. При работе с API и JSON часто встречается null, а undefined обычно не сериализуется.
Какие методы массива ты можешь назвать?Подробный перечень методов массивов в JavaScript:
- **mutating (изменяющие массив)**: `push()`, `pop()`, `shift()`, `unshift()`, `splice()`, `sort()`, `reverse()`, `copyWithin()`, `fill()`.
- **non‑mutating (не изменяющие исходный массив)**: `concat()`, `slice()`, `map()`, `filter()`, `reduce()`, `reduceRight()`, `every()`, `some()`, `find()`, `findIndex()`, `includes()`, `indexOf()`, `lastIndexOf()`, `join()`, `toString()`, `entries()`, `keys()`, `values()`, `flat()`, `flatMap()`, `forEach()`.
- **специальные методы**: `Array.from()`, `Array.isArray()`, `Array.of()`, `Array.prototype.at()`.
Кратко о каждом:
- `push(...items)` – добавить элементы в конец, вернуть новую длину.
- `pop()` – удалить последний элемент, вернуть его.
- `shift()` – удалить первый элемент, вернуть его.
- `unshift(...items)` – добавить в начало, вернуть новую длину.
- `splice(start, deleteCount, ...items)` – удалять/вставлять элементы, вернуть массив удалённых.
- `sort([compareFn])` – сортировать массив на месте.
- `reverse()` – обратить порядок элементов на месте.
- `copyWithin(target, start, end?)` – копировать часть массива в другую позицию внутри него.
- `fill(value, start?, end?)` – заполнить элементы одинаковым значением.
- `concat(...arraysOrValues)` – объединить массивы/значения, вернуть новый массив.
- `slice(begin, end?)` – получить часть массива, без изменения оригинала.
- `map(callback[, thisArg])` – создать новый массив, применив функцию к каждому элементу.
- `filter(callback[, thisArg])` – отфильтровать элементы, возвращая новый массив.
- `reduce(callback[, initialValue])` – свести массив к единому значению.
- `reduceRight(callback[, initialValue])` – как `reduce`, но справа налево.
- `every(callback[, thisArg])` – проверка, что все элементы удовлетворяют условию.
- `some(callback[, thisArg])` – проверка, что хотя бы один элемент удовлетворяет условию.
- `find(callback[, thisArg])` – вернуть первый элемент, удовлетворяющий условию.
- `findIndex(callback[, thisArg])` – вернуть индекс первого подходящего элемента.
- `includes(searchElement, fromIndex?)` – проверка наличия элемента.
- `indexOf(searchElement, fromIndex?)` – первый индекс найденного элемента или -1.
- `lastIndexOf(searchElement, fromIndex?)` – последний индекс найденного элемента или -1.
- `join(separator?)` – соединить элементы в строку.
- `toString()` – строковое представление массива.
- `entries()` – итератор пар `[index, value]`.
- `keys()` – итератор индексов.
- `values()` – итератор значений.
- `flat([depth])` – «разворачивает» вложенные массивы до указанной глубины.
- `flatMap(callback[, thisArg])` – комбинация `map` + `flat(1)`.
- `forEach(callback[, thisArg])` – выполнить функцию для каждого элемента без создания нового массива.
- `Array.from(iterable, mapFn?, thisArg?)` – создать массив из итерируемого объекта.
- `Array.isArray(arg)` – проверить, является ли значение массивом.
- `Array.of(...elements)` – создать массив из аргументов.
- `Array.prototype.at(index)` – получить элемент по положительному или отрицательному индексу.
Эти методы покрывают большинство операций над массивами в фронтенд-разработке.
Какие хуки тебе известны?React предоставляет набор встроенных хуков: `useState` — для управления локальным состоянием компонента; `useEffect` — для выполнения побочных эффектов и имитации жизненных методов; `useContext` — для доступа к контексту; `useReducer` — альтернатива `useState` при сложной логике обновления; `useCallback` — мемоизация функций; `useMemo` — мемоизация вычисляемых значений; `useRef` — хранение мутируемого значения между рендерами и доступ к DOM‑элементам; `useLayoutEffect` — синхронный эффект перед отрисовкой; `useImperativeHandle` — настройка публичного API для рефов; `useDebugValue` — вывод отладочной информации в React DevTools.

Помимо встроенных, часто создают пользовательские (custom) хуки, которые инкапсулируют повторно используемую логику: например, `useFetch` — для выполнения запросов и управления статусом загрузки; `useForm` — для управления формой и валидацией; `useLocalStorage` — синхронизация состояния с `localStorage`; `usePrevious` — получение предыдущего значения пропса/состояния; `useOnClickOutside` — обработка кликов вне элемента. Такие хуки комбинируют другие хуки, принимают параметры и возвращают нужные данные/функции, позволяя держать компоненты чистыми и лаконичными.
```json
В чём отличаются команды git rebase и git merge?`git merge` объединяет две ветки, создавая новый коммит‑слияние, который сохраняет историю обеих веток и показывает точку их объединения; при наличии конфликтов их нужно разрешать перед завершением слияния. `git rebase` переносит последовательность коммитов одной ветки на конец другой, переписывая историю: старые коммиты заменяются новыми, что делает граф линейным и упрощает просмотр, но меняет SHA‑1, поэтому его следует использовать только для локальных/неопубликованных веток; конфликты решаются в процессе перебазирования, после чего продолжается `git rebase --continue`. Основные различия: merge сохраняет ветвление и создаёт отдельный коммит‑merge, тогда как rebase переписывает историю, делая её линейной без дополнительного коммита; merge безопасен для публичных веток, rebase полезен для чистой истории перед отправкой.
Как работает и реализован механизм async/await в JavaScript?Async‑функция объявляется с ключевым словом async и всегда возвращает объект Promise. При её вызове код внутри функции исполняется синхронно до первого await. Оператор await принимает любой объект‑thenable (обычно Promise), приостанавливает выполнение функции, освобождая поток, и ставит оставшуюся часть функции в очередь микрозадач. После того как промис переходит в состояние fulfilled или rejected, его результат (или ошибка) помещается в очередь микрозадач, где планировщик событий (event loop) возобновляет выполнение функции, передавая полученное значение или бросая исключение. Таким образом async/await – синтаксический сахар над промисами, позволяющий писать асинхронный код в «плоском» стиле, похожем на синхронный.

Внутренняя реализация в современных движках (V8, SpiderMonkey и др.) преобразует async‑функцию в состояние машины (state machine), где каждый await соответствует точке перехода. При компиляции (например, Babel) async‑функция транспилируется в генератор и обёртывается функцией‑рантаймером (runtime), которая последовательно вызывает generator.next() после завершения каждого промиса, тем самым эмулируя поведение await. Это позволяет поддерживать async/await в старых браузерах, где нативной поддержки нет. Ошибки внутри async‑функции автоматически превращаются в отклонённый Promise, а конструкция try/catch работает как в синхронном коде, перехватывая исключения, возникшие после await.
Чем различаются операторы == и === в JavaScript?Оператор == (абстрактное сравнение) сравнивает два значения после их приведения к совместимому типу, что может приводить к неочевидным результатам (например, 0 == false → true, '' == 0 → true, null == undefined → true). Оператор === (строгое сравнение) проверяет как значение, так и тип без какого‑либо приведения, поэтому сравнение возвращает true только при полном совпадении (например, 0 === false → false, '' === 0 → false, null === undefined → false). Использование === рекомендуется для предсказуемого поведения, а == следует применять лишь в редких случаях, когда намеренно требуется нестрогое сравнение, обычно после явного приведения типов. Примеры:

```js
console.log(5 == '5'); // true (приведение строки к числу)
console.log(5 === '5'); // false (разные типы)

console.log(null == undefined); // true (особый случай)
console.log(null === undefined); // false

console.log([] == false); // true ([] → '' → 0, false → 0)
console.log([] === false); // false
```

Проблемы с == часто вызывают баги, особенно при работе с массивами, объектами и NaN (NaN == NaN → false). Для надёжного сравнения лучше всегда использовать === и при необходимости явно преобразовывать типы (Number(), String(), Boolean()).
В чём различаются методы массивов forEach и map?Метод forEach итерирует элементы массива и вызывает переданную функцию для каждого элемента, не создавая новый массив и не возвращая значения (возвращает undefined). Он предназначен для выполнения побочных эффектов (например, вывод в консоль, изменение внешних переменных). Метод map также проходит по всем элементам, но собирает результаты вызова функции в новый массив той же длины и возвращает его. map не изменяет исходный массив (если функция‑колбэк не мутирует элементы) и позволяет цепочкой применять дальнейшие преобразования. Выбор зависит от задачи: использовать forEach, когда нужен только побочный эффект, и map, когда требуется преобразовать массив и получить новый набор данных.
Назови все известные тебе хуки в React.В React существует несколько встроенных хуков, каждый из которых решает определённые задачи:

**Базовые хуки**
- `useState(initialState)` – добавляет в функциональный компонент локальное состояние и возвращает массив из текущего значения и функции‑сеттера.
- `useEffect(effect, deps?)` – заменяет жизненный цикл классовых компонентов (`componentDidMount`, `componentDidUpdate`, `componentWillUnmount`). Выполняет побочный эффект; массив зависимостей контролирует, когда эффект будет перезапущен.
- `useContext(Context)` – позволяет потреблять значение из контекста без использования `<Context.Consumer>`.

**Оптимизационные хуки**
- `useMemo(factory, deps)` – кэширует вычисленное значение между рендерами, пересчитывая его только при изменении зависимостей.
- `useCallback(callback, deps)` – кэширует функцию, возвращая ту же ссылку между рендерами, если зависимости не изменились.

**Работа с DOM и рефами**
- `useRef(initialValue)` – создаёт мутируемый объект `{ current: … }`, сохраняющий своё значение между рендерами; часто используется для доступа к DOM‑элементам.
- `useLayoutEffect(effect, deps?)` – аналог `useEffect`, но вызывается синхронно после всех изменений DOM, до отрисовки браузера.

**Управление состоянием**
- `useReducer(reducer, initialState, init?)` – альтернатива `useState` для сложных состояний; работает как `redux`‑подобный редьюсер внутри компонента.

**Дополнительные хуки (React 18+)**
- `useTransition()` – возвращает `[isPending, startTransition]`; позволяет помечать обновления как не‑приоритетные (для плавных UI).
- `useDeferredValue(value)` – откладывает обновление значения до тех пор, пока UI не станет свободным.
- `useId()` – генерирует уникальный, стабильный идентификатор, пригодный для атрибутов `id`/`htmlFor`.
- `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)` – безопасный способ подписки на внешние хранилища (например, Redux) с поддержкой серверного рендеринга.

**Пользовательские хуки**
- Любая функция, имя которой начинается с `use` и внутри которой вызываются другие хуки, считается пользовательским хуком. Они позволяют инкапсулировать повторно используемую логику (например, `useFetch`, `useLocalStorage`, `usePrevious`).

**Правила использования хуков (Rules of Hooks)**
1. Вызывайте хуки только на верхнем уровне компонента или внутри другого пользовательского хука – не помещайте их в условные блоки, циклы или вложенные функции.
2. Вызывайте хуки только из функциональных компонентов React или из пользовательских хуков.

Эти хуки покрывают большинство сценариев, от простого состояния до сложных асинхронных и оптимизационных задач.
Какие варианты хранения данных доступны в браузере?В браузере данные можно сохранять с помощью нескольких механизмов: **cookies** — небольшие строковые пары, передающиеся с каждым запросом к серверу, имеют ограничения по размеру (≈4 KB) и могут иметь срок жизни; **Web Storage** — два интерфейса — `localStorage` (постоянное хранение без срока действия, до 5‑10 МБ) и `sessionStorage` (живет только в рамках текущей вкладки); **IndexedDB** — асинхронная объектно‑ориентированная база данных, поддерживает большие объёмы (десятки‑сотни мегабайт), индексы и транзакции; **Cache API** (часть Service Workers) — позволяет кэшировать запросы и ответы, что полезно для офлайн‑режима; **File System Access API** — предоставляет ограниченный доступ к файловой системе пользователя для чтения/записи файлов; **WebSQL** — устаревший SQL‑подобный интерфейс, поддерживается только в некоторых браузерах и не рекомендуется к использованию. Каждый из этих способов имеет свои ограничения по объёму, доступности, синхронности/асинхронности и безопасности, поэтому выбор зависит от конкретных требований приложения.
Что такое микрозадача и как она работает в JavaScript?Микрозадача — это задача, помещаемая в отдельную очередь (microtask queue) event loop‑а и исполняемая сразу после завершения текущего стека вызовов, но до обработки следующего макротаска (macrotask). К микрозадачам относятся колбэки `Promise.then`, `Promise.catch`, `Promise.finally`, функции, переданные в `queueMicrotask`, а также задачи, генерируемые `MutationObserver` и `queueMicrotask`. После того как стек вызовов опустеет, движок последовательно извлекает и выполняет все микрозадачи из очереди, гарантируя их порядок FIFO. Только после исчерпания микрозадач происходит переход к следующему макротаску (например, `setTimeout`, `setInterval`, I/O). Это обеспечивает более предсказуемое и быстрый порядок выполнения асинхронных операций, позволяя, например, завершать цепочки промисов до отрисовки UI.
Что подразумевается под жизненным циклом компонента в React?Жизненный цикл компонента — это последовательность фаз, через которые проходит компонент от создания до уничтожения, и набор методов/хуков, позволяющих реагировать на каждую из этих фаз.

**Классовые компоненты**
1. **Mounting (монтирование)** – создание и добавление в DOM:
- `constructor(props)` – инициализация состояния и привязка методов.
- `static getDerivedStateFromProps(props, state)` – синхронизация state с пропсами перед рендером.
- `render()` – формирует JSX.
- `componentDidMount()` – вызывается после того, как элемент вставлен в DOM; здесь обычно делают запросы к API, подписки, таймеры.

2. **Updating (обновление)** – изменение props или state:
- `static getDerivedStateFromProps` (вызывается каждый раз).
- `shouldComponentUpdate(nextProps, nextState)` – позволяет оптимизировать ререндер, возвращая `false` при отсутствии изменений.
- `render()` – повторный рендер.
- `getSnapshotBeforeUpdate(prevProps, prevPrevState)` – позволяет захватить текущие значения (например, позицию скролла) перед изменением DOM.
- `componentDidUpdate(prevProps, prevState, snapshot)` – вызывается после обновления DOM; часто используется для побочных эффектов, зависящих от новых данных.

3. **Unmounting (размонтирование)** – удаление из DOM:
- `componentWillUnmount()` – очистка подписок, таймеров, отмена запросов.

4. **Error handling** – обработка ошибок в дочерних компонентах:
- `static getDerivedStateFromError(error)` – обновляет state, чтобы отобразить fallback UI.
- `componentDidCatch(error, info)` – логирует ошибку.

**Функциональные компоненты (Hooks)**
- `useEffect(() => { … }, [])` – эквивалент `componentDidMount` и `componentWillUnmount` (при возврате функции‑очистки).
- `useEffect(() => { … }, [deps])` – реагирует на изменения зависимостей, заменяя `componentDidUpdate`.
- `useLayoutEffect` – синхронный эффект, выполняется перед отрисовкой, аналог `componentDidMount`/`componentDidUpdate` в случае необходимости измерения DOM.
- `useMemo`, `useCallback` – оптимизируют вычисления и функции между рендерами, аналогично `shouldComponentUpdate`.
- `useReducer` – управляет сложным состоянием, заменяя `setState` в классовых компонентах.

**Ключевые моменты**
- Фаза монтирования происходит один раз; обновление может происходить многократно; размонтирование вызывается единожды перед удалением.
- При использовании хуков важно правильно указывать массив зависимостей, иначе эффект будет вызываться каждый рендер или, наоборот, никогда.
- Очистка в `useEffect` (или `componentWillUnmount`) предотвращает утечки памяти.
- Для оптимизации рендера в классах используют `PureComponent` или `React.memo` в функциях, а в функциях —  `React.memo` вместе с `useCallback`/`useMemo`.
В чём различаются CSS‑Grid и Flexbox?CSS‑Grid — двумерная система раскладки, позволяющая размещать элементы как по строкам, так и по колонкам, задавать сетку контейнера через свойства grid‑template‑rows, grid‑template‑columns, grid‑area и управлять позиционированием отдельных ячеек с помощью grid‑row‑start/end, grid‑column‑start/end, а также автоматически размещать элементы в заданных областях. Flexbox — одномерная модель, ориентированная на размещение элементов вдоль главной оси (row или column) и выравнивание их по поперечной оси; управляется свойствами display: flex, flex‑direction, flex‑wrap, justify‑content, align‑items, align‑content, а отдельные элементы — через flex‑grow, flex‑shrink, flex‑basis, order. Grid лучше подходит для сложных макетов с фиксированными/адаптивными колонками и строками (например, страницы с боковыми панелями, галереями, формами), в то время как Flexbox удобнее для линейных компонентов (навигация, карточки, кнопки) и динамического распределения пространства. Grid позволяет задавать промежутки (grid‑gap) и управлять областями (named grid areas), а Flexbox — gap (поддерживается в новых браузерах) и автоматический перенос (flex‑wrap). Оба метода совместимы со всеми современными браузерами, но Grid имеет более ограниченную поддержку в старых версиях IE (только частичный). Приоритет выбора зависит от задачи: если нужен контроль над двумя измерениями — выбираем Grid; если требуется простое выравнивание в одну строку/колонку — Flexbox. Комбинирование обоих подходов часто используется: Grid задаёт общую структуру страницы, а Flexbox — внутри отдельных ячеек.
Какие этапы и процессы происходят в браузере при выполнении HTTP‑запроса?При вводе URL браузер сначала проверяет кеш DNS; если запись отсутствует, отправляет запрос к DNS‑серверу для получения IP‑адреса. Затем устанавливается TCP‑соединение (трёхстороннее рукопожатие SYN‑SYN‑ACK‑ACK). При HTTPS дополнительно проводится TLS‑рукопожатие: обмен сертификатами, проверка подлинности, генерация сеансового ключа. После этого браузер формирует HTTP‑запрос (метод, заголовки, тело) и отправляет его серверу. Сервер обрабатывает запрос, генерирует ответ (статус, заголовки, тело) и возвращает его клиенту. Браузер получает ответ, проверяет коды статуса, сохраняет ресурсы в кеш, а затем начинается процесс рендеринга: парсинг HTML в DOM‑дерево, парсинг CSS в CSS‑OM, построение render‑tree, вычисление layout, отрисовка (paint) и композитинг. Параллельно запускаются JavaScript‑движки (парсинг, компиляция, исполнение), могут происходить дополнительные сетевые запросы (AJAX, fetch), обработка событий и обновление UI. Весь цикл управляется событийному циклу (event loop) и может включать асинхронные операции, сервис‑воркеры, кеширование и оптимизации (HTTP/2, SPDY, Brotli).
Как определяется область видимости переменных и функций в коде?Область видимости (scope) — это контекст, в котором объявленная переменная, функция или параметр доступны для обращения. В JavaScript (и в большинстве фронтенд‑языков) различают несколько видов областей видимости: **глобальная** (переменные, объявленные в корневом контексте, доступны везде), **функциональная** (переменные, объявленные внутри функции, видимы только в ней и во вложенных функциях), **блочная** (введена в ES6 – `let` и `const` ограничивают видимость переменной только текущим блоком `{}`), **модульная** (переменные, экспортируемые/импортируемые через ES‑модули, видимы только внутри соответствующего модуля).

Область видимости определяется **лексически**: интерпретатор анализирует структуру кода во время компиляции/парсинга, а не во время выполнения. Поэтому вложенные функции «замыкают» (closure) переменные из внешних областей, сохраняют их значения даже после завершения внешней функции.

Особенности:
- **Хостинг**: объявления `var` поднимаются (hoisted) в начало функции, но инициализация остаётся на месте; `let`/`const` находятся в «временной мертвой зоне» до выполнения их объявления.
- **Перезапись**: переменные из внешних областей могут быть переопределены в вложенных, если не используется `const`.
- **Потеря контекста**: при передаче метода как колбэка `this` может изменить область видимости, поэтому используют `bind`, стрелочные функции или сохраняют `self = this`.
- **Модули**: каждый ES‑модуль имеет собственную топ‑уровневую область видимости, изолируя переменные от глобального скрипта.

Практические рекомендации:
1. По возможности использовать `const`, затем `let` вместо `var` для предсказуемой блочной области видимости.
2. Минимизировать количество глобальных переменных, помещая код в модули или IIFE.
3. Явно передавать зависимости в функции, а не полагаться на внешние переменные.
4. При работе с асинхронным кодом помнить, что замыкание сохраняет ссылки на переменные, а не их мгновенные значения.

Пример:
```js
let count = 0; // глобальная (в модуле) область видимости
function outer() {
const step = 2; // функциональная область видимости outer
for (let i = 0; i < 5; i++) { // блочная область видимости цикла
setTimeout(() => console.log(i + step), i * 100);
}
}
outer(); // выводит 2,3,4,5,6
```
Здесь `i` доступен только внутри цикла, `step` – внутри `outer`, а `count` – вне функций.

Таким образом, область видимости определяет, где в коде можно обратиться к тому или иному идентификатору, управляя доступом, изоляцией и временем жизни данных.
Что подразумевается под термином «прототип» в программировании?Прототип — это объект, из которого другие объекты наследуют свойства и методы через прототипную цепочку. В JavaScript каждый объект имеет внутреннее свойство [[Prototype]], которое указывает на другой объект (прототип). При попытке доступа к свойству интерпретатор сначала ищет его в самом объекте, а если не находит — переходит к его прототипу, затем к прототипу прототипа и так далее, пока не достигнет null. Прототипы реализуют наследование без классов: функции‑конструкторы автоматически задают свойство prototype, которое будет использовано в качестве [[Prototype]] для созданных экземпляров. Методы можно добавить в FunctionName.prototype, чтобы они были общими для всех экземпляров. Свойство __proto__ (или Object.getPrototypeOf / Object.setPrototypeOf) позволяет читать и менять прототип уже существующего объекта, однако изменение прототипа считается дорогой операцией и её следует избегать в производительном коде. Для создания объектов с заданным прототипом используется Object.create(proto, properties), что позволяет построить цепочку наследования вручную без функции‑конструктора. Прототипное наследование отличается от классического (классового) тем, что свойства и методы хранятся в едином объекте‑прототипе, а не копируются в каждый экземпляр, что экономит память и позволяет динамически менять поведение всех наследников сразу.
Какие способы оптимизации производительности ты знаешь в React?;Использовать React.memo и PureComponent для предотвращения лишних рендеров; применять useCallback и useMemo для мемоизации функций и вычислений; разбивать код с помощью React.lazy и Suspense (code‑splitting); внедрять динамический импорт и lazy‑loading компонентов; оптимизировать списки через виртуализацию (react‑window, react‑virtualized); правильно задавать key в списках; избегать создания новых объектов/массивов в props; использовать shouldComponentUpdate или memo‑обёртки; применять неизменяемые структуры данных (Immutable.js, immer); минимизировать использование контекста или разбивать его на мелкие провайдеры; профилировать приложение с React DevTools и профайлером; включать Concurrent Mode и использовать useTransition для отложенных обновлений; применять серверный рендеринг (SSR) и предварительный рендеринг (pre‑render) для ускорения первого рендеринга. ;["React","performance","optimizations","hooks","memoization"];Junior, Middle, Senior
Какими технологиями и инструментами ты пользуешься в своей работе?В качестве фронтенд‑разработчика я работаю с HTML5, CSS3 (Flexbox, Grid, Sass/LESS), JavaScript (ES6+), а также с современными фреймворками и библиотеками — React (Hooks, Context API, Redux, React Router), Vue.js (Composition API, Vuex, Vue Router) и Angular (TypeScript, RxJS, NgRx). Для сборки и автоматизации использую Webpack, Vite, Parcel, npm/Yarn/PNPM. Тестирование покрываю Jest, React Testing Library, Cypress. Для стилизации применяю CSS‑модули, Styled‑Components, Tailwind CSS. Работаю с RESTful API, GraphQL, WebSocket, системами контроля версий Git и CI/CD (GitHub Actions, GitLab CI).
Что представляет собой хук useRef?useRef — это встроенный React‑хук, который возвращает мутируемый объект { current: … }. Этот объект сохраняётся между рендерами компонента и может использоваться для: 1) хранения произвольных значений, которые не вызывают повторный рендер при изменении; 2) получения ссылки на DOM‑элемент (через ref‑атрибут). При вызове useRef(initialValue) в current помещается initialValue; последующие изменения current не влияют на фазу рендеринга. Отличие от useState: изменение состояния приводит к повторному рендеру, а изменение ref — нет. Пример использования для доступа к элементу ввода:

```jsx
function MyComponent() {
const inputRef = useRef(null);

const focus = () => {
if (inputRef.current) inputRef.current.focus();
};

return (
<>
<input ref={inputRef} type="text" />
<button onClick={focus}>Фокус</button>
</>
);
}
```

Пример хранения mutable‑значения:

```jsx
const countRef = useRef(0);
useEffect(() => {
const id = setInterval(() => {
countRef.current += 1; // не вызывает перерисовку
console.log(countRef.current);
}, 1000);
return () => clearInterval(id);
}, []);
```

Важно помнить, что ref‑объекты не сериализуются, их нельзя использовать в качестве зависимостей эффекта без обёртки, а при работе с DOM‑элементами следует проверять наличие current.
Что представляет собой хук useState в React?useState — это встроенный хук React, позволяющий функциональному компоненту иметь собственное состояние. При вызове useState(initialValue) он возвращает массив из двух элементов: текущего значения состояния и функции‑обновления setState. При вызове setState(newValue) React планирует повторный рендер компонента, при этом новое значение сохраняется и будет доступно в следующем рендере. Хук сохраняет своё состояние между рендерами, независимо от количества вызовов компонента, благодаря внутреннему механизму «hooks» и порядку их вызова. Синтаксис: const [state, setState] = useState(initialValue);. initialValue может быть любой величиной: примитив, объект, массив или функция‑ленивый инициализатор (function, возвращающая начальное значение). При обновлении состояния можно передать новое значение напрямую или функцию‑обновления, получающую предыдущее состояние: setState(prev => prev + 1). Хук следует вызывать только на верхнем уровне компонента и не внутри условных операторов, циклов или вложенных функций, чтобы порядок вызовов оставался стабильным. useState часто комбинируют с другими хуками (useEffect, useMemo) для управления побочных эффектов и оптимизации. Пример:
```js
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
);
}
```
useState является фундаментальной частью современной React‑архитектуры и заменяет необходимость в классах и this.state.
Для чего в хуке useEffect нужен массив зависимостей?Массив зависимостей в useEffect определяет, при каких изменениях значений из замыкания эффект должен пересчитываться. Если массив пустой — эффект выполнится только один раз после монтирования компонента. Если в массиве указаны переменные, React сравнивает их значения между рендерами и запускает эффект заново только тогда, когда хотя бы одна из зависимостей изменилась. Это позволяет избегать лишних вызовов, синхронно обновлять данные, управлять подписками, таймерами и другими побочными эффектами, а также корректно выполнять очистку через функцию, возвращаемую из эффекта. Без массива эффект будет вызываться после каждого рендера, что часто приводит к бесконечным циклам, утечкам памяти и ухудшению производительности. Кроме того, массив зависимостей используется линтером eslint-plugin-react-hooks для статической проверки корректности зависимостей.
Что такое метод apply в JavaScript и как он работает?`apply` — это встроенный метод функции (`Function.prototype.apply`), который позволяет вызвать функцию с явно заданным значением `this` и передать ей аргументы в виде массива или array‑похожего объекта. Синтаксис: `func.apply(thisArg, argsArray)`. `thisArg` определяет контекст выполнения, а `argsArray` – массив (или объект, поддерживающий индексацию и свойство `length`) аргументов; если `argsArray` равно `null` или `undefined`, аргументы не передаются. `apply` отличается от `call` тем, что `call` принимает аргументы перечисленно (`func.call(thisArg, arg1, arg2, ...)`), тогда как `apply` получает их в виде одного массива, что удобно при работе с динамически сформированными списками аргументов (например, `Math.max.apply(null, numbers)`). Пример:

```js
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Аня' };
greet.apply(person, ['Привет', '!']); // Выведет: Привет, Аня!
```

`apply` часто используют для:
- передачи массива аргументов в функции, которые их принимают по отдельности;
- реализации наследования (вызов конструктора родителя внутри дочернего с `Parent.apply(this, arguments)`);
- применения методов массивов к объектам‑псевдомассивам (`Array.prototype.slice.apply(arguments)`).

В современных браузерах и Node.js `apply` имеет одинаковую производительность с `call`, но при работе с большим количеством аргументов иногда предпочтительнее использовать оператор распространения (`...args`) вместо `apply`. Также стоит помнить, что `apply` нельзя вызвать у стрелочных функций, так как у них нет собственного `this`.

```js
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
// sum.apply(null, [1, 2, 3]); // Ошибка: стрелочная функция игнорирует this, но аргументы работают
```

Для фиксированного контекста часто используют `bind`, который возвращает новую функцию с предустановленным `this` и (опционально) частично применёнными аргументами.

Итого, `apply` – мощный инструмент управления контекстом и передачей аргументов, особенно полезный при работе с динамическими наборами параметров и при реализации паттернов наследования.
Какие отличия существуют между класс‑компонентами и функциональными компонентами в React?Класс‑компоненты объявляются как ES6‑классы, наследуя React.Component, и используют методы жизненного цикла (componentDidMount, shouldComponentUpdate и т.д.). Состояние и методы привязываются к экземпляру через this.state и this.setState, а обработчики часто требуют биндинга. Функциональные компоненты – это простые функции, принимающие props и возвращающие JSX. До появления хуков они были «без состояния», но с хуками (useState, useEffect, useContext, useReducer и др.) они могут управлять локальным состоянием, выполнять побочные эффекты и использовать контекст, заменяя почти все возможности классов. Функциональные компоненты обычно короче, легче читаются, не требуют привязки this и лучше оптимизируются React‑ом (например, благодаря React.memo). Кроме того, при использовании хуков можно разделять логику на переиспользуемые кастомные хуки, чего нет в классах. В новых проектах рекомендуется использовать функциональные компоненты, так как они являются современной практикой и упрощают поддержку кода.
Каковы основные различия между примитивными типами и типами‑ссылками в Java?Примитивные типы (byte, short, int, long, float, double, char, boolean) хранят непосредственно значение в стеке, имеют фиксированный размер, не могут быть null и копируются при передаче в методы, что приводит к передаче копии значения. Ссылочные типы (классы, массивы, интерфейсы) хранят в стеке лишь ссылку на объект, размещённый в куче; они могут принимать значение null, их копирование передаёт лишь копию ссылки, поэтому изменения внутри метода видны снаружи. Примитивы обладают предопределёнными значениями по умолчанию (0, false, '\u0000'), а ссылки – null. Для каждого примитивного типа существуют обёрточные классы (Integer, Double и т.д.), позволяющие использовать их как объекты, поддерживая автоупаковку/распаковку (boxing/unboxing). Ссылочные типы поддерживают наследование, полиморфизм и могут иметь методы и состояние, в то время как примитивы не имеют методов. При работе с коллекциями Java требует использования ссылочных типов, поэтому примитивы автоматически преобразуются в их обёртки. Управление памятью для объектов осуществляется сборщиком мусора, тогда как примитивы освобождаются автоматически при выходе из области видимости.
Какие элементы составляют HTTP‑запрос?HTTP‑запрос состоит из следующих частей:
1. **Стартовая строка (Request Line)** – первая строка, в которой указываются метод (GET, POST, PUT, DELETE и т.д.), целевой URI (путь и, при необходимости, строка запроса) и версия протокола HTTP (например, HTTP/1.1).
2. **Заголовки (Headers)** – набор пар «ключ: значение», передающих метаданные запроса: Host, User‑Agent, Accept, Content‑Type, Authorization, Cookie и прочие. Каждый заголовок располагается на отдельной строке и завершается переводом строки.
3. **Пустая строка** – разделитель между заголовками и телом сообщения; указывает конец секции заголовков.
4. **Тело запроса (Message Body)** – необязательная часть, содержащая данные, передаваемые серверу (например, JSON, form‑data, файлы). Тело присутствует в запросах методов, которые могут иметь payload (POST, PUT, PATCH), и его наличие определяется заголовками Content‑Length или Transfer‑Encoding.

Дополнительно в запросе могут присутствовать:
- **Параметры строки запроса** (часть URI после «?», используемая для передачи коротких данных).
- **Куки** (передаются в заголовке Cookie).
- **Трассировочные заголовки** (например, X‑Forwarded‑For) для прокси‑и балансировочных решений.

Таким образом, полный HTTP‑запрос выглядит как:

```
<Метод> <Путь> <HTTP/Версия>\r\n
<Заголовок‑1>: <значение>\r\n
<Заголовок‑2>: <значение>\r\n
...
\r\n
<Тело запроса (при наличии)>
```
Назови известные тебе паттерны проектирования.Существует три основных группы паттернов: порождающие (Singleton — гарантирует единственный экземпляр, Factory Method — создаёт объекты через интерфейс, Abstract Factory — семейство связанных объектов, Builder — пошаговое построение сложных объектов, Prototype — клонирование), структурные (Adapter — приводит несовместимые интерфейсы к общему, Bridge — разделяет абстракцию и реализацию, Composite — работа с объектами как с деревом, Decorator — добавление поведения без наследования, Facade — упрощённый интерфейс к подсистеме, Flyweight — разделение общего состояния, Proxy — заместитель для контроля доступа) и поведенческие (Observer — оповещение подписчиков, Strategy — выбор алгоритма во время выполнения, Command — инкапсуляция запросов, Iterator — поэлементный обход, State — изменение поведения при смене состояния, Template Method — шаблон алгоритма, Visitor — операции над элементами структуры без изменения её классов, Memento — сохранение и восстановление состояния, Chain of Responsibility — передача запросов по цепочке обработчиков). В контексте фронтенда часто используют архитектурные паттерны: MVC, MVVM, MVP, Flux/Redux для управления состоянием, а также React‑специфичные паттерны — Higher‑Order Component, Render Props, Hook pattern, Compound Component, Provider pattern. Каждый паттерн решает конкретную задачу: упрощает тестирование, повышает переиспользуемость, уменьшает связанность и облегчает масштабирование приложения.
Какие свойства position существуют в CSS?Свойство `position` в CSS может принимать следующие значения: `static` — элемент располагается в обычном потоке документа; `relative` — элемент остаётся в потоке, но может быть смещён относительно своего обычного положения с помощью `top`, `right`, `bottom`, `left`, при этом сохраняет место для последующих элементов; `absolute` — элемент выводится из потока и позиционируется относительно ближайшего предка с ненулевым позиционированием (`relative`, `absolute`, `fixed` или `sticky`), либо относительно окна просмотра, если такого предка нет; `fixed` — элемент фиксируется относительно окна браузера и не смещается при прокрутке; `sticky` — элемент ведёт себя как `relative`, пока не достигнет заданного порога прокрутки, после чего «прилипает» как `fixed`; также поддерживаются глобальные значения `inherit`, `initial`, `revert`, `unset`. При использовании `absolute`, `fixed` и `sticky` свойства `top`, `right`, `bottom`, `left` определяют смещение, а `z-index` управляет порядком наложения в контексте стэка.
Какими способами можно копировать объекты в JavaScript?Для копирования объектов в JavaScript существуют различные методы, каждый из которых подходит для определённых сценариев:
- **Поверхностное (shallow) копирование**:
• `Object.assign({}, source)` – копирует перечислимые собственные свойства из `source` в новый объект; вложенные объекты остаются ссылками.
• Оператор распространения (spread) `{...source}` – аналогично `Object.assign`, удобен для копирования объектов и массивов.
• `Array.prototype.slice()` или `[...array]` – создают поверхностную копию массива.
- **Глубокое (deep) копирование**:
• `JSON.parse(JSON.stringify(source))` – простой способ, но теряет функции, `undefined`, `Symbol`, `Date`, `RegExp` и циклические ссылки.
• `structuredClone(source)` (современный API) – рекурсивно копирует большинство типов, поддерживает `Date`, `Map`, `Set`, `ArrayBuffer` и сохраняет циклические ссылки; работает в современных браузерах.
• Библиотеки: `lodash.cloneDeep(source)`, `ramda.clone(source)`, `rfdc()` (быстрый deep‑clone) – предоставляют надёжные глубокие копии с поддержкой разных типов данных.
• Рекурсивные функции, написанные вручную, позволяют полностью контролировать процесс копирования, учитывая специфические типы (например, копию классов, прототипов).
- **Копирование с сохранением прототипа**:
• `Object.create(Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source))` – создаёт новый объект с тем же прототипом и теми же свойствами, но без копирования вложенных объектов (поверхностное).
- **Копирование специализированных структур**:
• `Map` и `Set` можно копировать через конструктор: `new Map(oldMap)` / `new Set(oldSet)`.
• `TypedArray` копируется методом `slice()`: `new Uint8Array(oldArray)` или `oldArray.slice()`.
- **Важно учитывать**:
• При поверхностном копировании изменение вложенных объектов отражается в оригинале.
• При глубоком копировании следует помнить о производительности и размере структуры.
• Некоторые методы не копируют свойства с геттерами/сеттерами или свойства, определённые через `Symbol`.
• Циклические ссылки требуют специальных решений (`structuredClone` или пользовательская рекурсия с трекингом).
Таким образом, выбор метода копирования зависит от требуемой глубины копии, поддерживаемых типов данных, необходимости сохранения прототипов и ограничений среды выполнения.
Какими способами можно объявлять переменные в JavaScript?В JavaScript переменные объявляются с помощью ключевых слов **var**, **let** и **const**. `var` создаёт переменную с функцией‑уровнем (или глобальной) области видимости и подвержена поднятию (hoisting); её значение можно переопределять и переназначать. `let` вводит переменную блочного уровня, также поднимается, но находится в «временной мёртвой зоне» до инициализации, и её значение можно менять, но нельзя переопределять объявление в той же области. `const` объявляет блочную константу, требующую инициализации при объявлении; её привязка неизменна, хотя содержимое объекта или массива может изменяться. Кроме явных объявлений, переменные могут появляться неявно, если присвоить значение необъявленной идентификатору в не‑строгом режиме (создаётся глобальная переменная). Также возможны объявления через деструктурирующее присваивание массивов и объектов, параметрами функций и полями классов (public, private, static). Для каждой формы существуют особенности поведения в отношении области видимости, поднятия, «временной мёртвой зоны» и возможности переопределения.
Какими библиотеками или подходами для управления состоянием ты пользуешься?Для управления состоянием в фронтенде существует множество решений, каждое из которых подходит под определённые задачи и архитектурные требования. Наиболее популярные менеджеры состояния: **Redux** — централизованный хранилище с предсказуемыми изменениями через actions и reducers; **MobX** — реактивный подход с наблюдаемыми данными и автоматическим отслеживанием зависимостей; **Zustand** — лёгкий, безболезненный API, использующий функции‑сеттеры; **Recoil** — атомарные единицы состояния, интегрированные с React‑контекстом; **Jotai** — атомы как минимальный набор примитивов; **XState** — управление конечными автоматами и сложными бизнес‑процессами; а также нативный **React Context API** в сочетании с хуками (`useState`, `useReducer`). Выбор зависит от масштаба проекта, необходимости в масштабируемости, типе данных (глобальное vs локальное), требований к типизации (TypeScript) и предпочтений команды. Например, в больших приложениях с множеством взаимосвязанных данных часто выбирают Redux с Toolkit для упрощения boilerplate; в небольших проектах предпочтительнее Context + `useReducer` или Zustand из‑за простоты. При работе с асинхронными процессами полезны middleware (redux‑thunk, redux‑saga) или встроенные возможности, как `atomFamily` в Recoil. Кроме того, важно учитывать отладку (Redux DevTools), тестируемость и возможность сервер‑сайд рендеринга.
Какие базовые принципы объектно‑ориентированного программирования (ООП) тебе известны?Объектно‑ориентированное программирование основывается на четырёх ключевых принципах: **инкапсуляция** (скрытие внутреннего состояния и реализаций объекта за публичным интерфейсом), **наследование** (создание новых классов на основе существующих, переиспользование и расширение их поведения), **полиморфизм** (возможность объектов разных классов реагировать на одинаковые сообщения/методы по‑разному, включая переопределение методов и перегрузку), **абстракция** (выделение существенных характеристик предмета и игнорирование деталей). На практике в фронтенде часто используют также **композицию** вместо наследования, а принципы SOLID (особенно SRP, OCP, DIP) помогают писать более гибкий и поддерживаемый код.
Что происходит после ввода URL‑адреса в строку браузера?При вводе адреса браузер разбирает URL, проверяет кэш, инициирует DNS‑разрешение домена, устанавливает TCP‑соединение (с TLS‑рукопожатием для HTTPS), формирует HTTP‑запрос и отправляет его на сервер. Сервер обрабатывает запрос, генерирует ответ (HTML, CSS, JS, медиа и т.д.) и возвращает его. Браузер получает поток данных, декодирует его, парсит HTML в DOM‑дерево, CSS в CSSOM, объединяет их в рендер‑дерево, выполняет layout, рисует (paint) и композитирует слои. Параллельно запускается JavaScript‑движок (парсинг, компиляция, исполнение), могут срабатывать сервис‑воркеры, запросы к кэшу, а также происходит загрузка дополнительных ресурсов (изображения, шрифты, запросы AJAX). После завершения всех фаз контент отображается пользователю, а браузер продолжает управлять интерактивностью и обновлениями страницы.
Что представляет собой Utility type?Utility type — это предопределённый или пользовательский обобщённый тип в TypeScript, предназначенный для преобразования, модификации или извлечения свойств из существующих типов; они позволяют быстро создавать новые типы без повторного описания структуры, реализуют такие операции, как Partial<T> (делает все свойства необязательными), Required<T> (делает их обязательными), Readonly<T> (делает свойства только для чтения), Pick<T, K> (выбирает подмножество свойств), Omit<T, K> (исключает свойства), Record<K, T> (создаёт тип‑словарь), Exclude<T, U>, Extract<T, U>, NonNullable<T>, ReturnType<T>, Parameters<T> и др.; пользовательские utility types могут быть построены с помощью условных типов, операторов распределения и рекурсии, что делает их мощным инструментом для типовой рефакторинга и повышения типовой безопасности в больших фронтенд‑проектах.
Что представляет собой оператор rest в JavaScript?Оператор rest (три точки `...` перед параметром функции или при деструктуризации) собирает «оставшиеся» элементы в массив (для массивов) или объект (для объектов). В функциях он позволяет объявлять параметр, который будет содержать все переданные аргументы, не указанные явно: `function fn(a, b, ...rest) {}` – здесь `rest` будет массивом всех аргументов, начиная с третьего. При деструктуризации массивов: `const [first, ...others] = arr;` – `others` получает все элементы, кроме первого. При деструктуризации объектов: `const {x, y, ...restProps} = obj;` – `restProps` будет объектом со всеми свойствами, кроме `x` и `y`. Оператор rest всегда должен стоять последним в списке параметров или в паттерне деструктуризации, иначе будет синтаксическая ошибка. Он отличается от spread‑оператора, который «разворачивает» массив или объект в отдельные элементы (например, `fn(...arr)` или `{...obj}`). Примеры:

```js
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

const [head, ...tail] = [10, 20, 30];
console.log(head); // 10
console.log(tail); // [20, 30]

const {a, ...restObj} = {a: 1, b: 2, c: 3};
console.log(a); // 1
console.log(restObj); // {b: 2, c: 3}
```

Таким образом, rest‑оператор упрощает работу с переменным числом аргументов и с частичным извлечением данных из массивов/объектов.
Что подразумевается под всплытием события?Всплытие (bubbling) — это механизм распространения события от самого вложенного (целевого) элемента к его предкам в дереве DOM. После того как событие происходит на целевом элементе, оно последовательно передаётся его родительскому узлу, затем родителю этого узла и так далее, пока не достигнет корня документа. В процессе всплытия можно перехватывать событие на любом из промежуточных уровней, используя методы `addEventListener` без указания флага `capture`, а также останавливать дальнейшее распространение с помощью `event.stopPropagation()` или полностью отменять действие по умолчанию с `event.preventDefault()`. Всплытие отличается от захвата (capturing), когда событие сначала проходит путь от корня к целевому элементу. Всплытие широко используется для делегирования событий: один обработчик, навешенный на общий родительский элемент, может обрабатывать события всех его дочерних элементов, что упрощает код и повышает производительность.
Что такое мемоизация и как она работает?Мемоизация — это техника оптимизации, при которой результаты дорогостоящих функций кэшируются на основе их входных параметров, чтобы при повторных вызовах с теми же аргументами возвращать уже вычисленное значение без повторных вычислений. В JavaScript мемоизацию обычно реализуют через замыкания: создают внутренний объект‑кеш, в котором ключом служит сериализованное представление аргументов, а значением — результат функции. При вызове проверяют, есть ли запись в кеше; если есть — возвращают её, если нет — вычисляют результат, сохраняют в кеш и возвращают. Мемоизация полезна для чистых (pure) функций, которые всегда возвращают одинаковый результат для одинаковых входов и обладают высокой стоимостью вычислений (например, рекурсивные алгоритмы, работа с большими массивами, парсинг, сложные вычисления в UI). Отличие от общего кэширования — мемоизация привязывается к конкретной функции и её аргументам, а не к произвольным запросам. При использовании следует учитывать объём памяти (кеш может расти бесконечно), необходимость сброса кеша (например, при изменении внешних данных) и корректную сериализацию аргументов (особенно для объектов). В современных библиотеках (lodash, memoizee, React.memo, useMemo) предоставляются готовые реализации мемоизации.
Что такое рекурсия в программировании?Рекурсия — это метод решения задачи, при котором функция вызывает сама себя с изменёнными аргументами, пока не будет достигнуто условие завершения (база рекурсии). При каждом вызове создаётся новый фрейм стека вызовов, в котором сохраняются параметры и контекст выполнения. Основные элементы рекурсивного алгоритма: база (условие, при котором дальнейшие вызовы прекращаются) и рекурсивный шаг (вызов функции с «более простыми» данными). Пример в JavaScript:
```js
function factorial(n) {
if (n <= 1) return 1; // базовый случай
return n * factorial(n-1); // рекурсивный шаг
}
```
При вычислении `factorial(5)` стек будет содержать вызовы `factorial(5) → factorial(4) → … → factorial(1)`, после чего результаты будут возвращаться обратно.

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

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

Важно помнить о корректном определении базового случая и о том, что каждый рекурсивный вызов должен приближать задачу к базовому условию, иначе произойдёт бесконечный цикл и ошибка «Maximum call stack size exceeded».
Что подразумевается под термином «семантика» в веб‑разработке?Семантика — это смысловое значение элементов разметки и кода, которое передаёт их назначение как для браузеров, так и для людей (пользователей, поисковых систем, вспомогательных технологий). В контексте фронтенда семантика проявляется в правильном выборе HTML‑тегов, атрибутов и структурных паттернов, отражающих роль содержимого:
- **Смысловые (семантические) теги** — `<header>`, `<nav>`, `<main>`, `<section>`, `<article>`, `<aside>`, `<footer>`, `<figure>`, `<figcaption>`, `<time>` и др. Они описывают тип и функцию блока, облегчая навигацию и индексацию.
- **Неструктурированные (несемантические) теги** — `<div>`, `<span>` — используются лишь как контейнеры без указания смысла; их избыточное применение ухудшает доступность и SEO.
- **Атрибуты** — `role`, `aria-*`, `lang`, `title` усиливают семантику, делая интерфейс понятным скрин‑ридерам и другим вспомогательным средствам.
- **Польза семантики**:
1. **Доступность**: скрин‑ридеры могут правильно объявлять структуру страницы, пользователи с ограниченными возможностями получают корректную навигацию.
2. **SEO**: поисковые роботы используют семантическую разметку для определения важности и контекста контента, что повышает рейтинг.
3. **Поддерживаемость**: код становится самодокументируемым, легче читаемым и масштабируемым для командной разработки.
4. **Производительность**: браузеры могут более эффективно обрабатывать известные структуры, ускоряя рендеринг.
- **Best‑practice**:
* Выбирать наиболее специфичный тег, соответствующий содержимому.
* Избегать излишних вложений `<div>`‑ов; использовать вложенные семантические элементы.
* Добавлять ARIA‑ролы только когда стандартных семантических тегов недостаточно.
* Обеспечивать корректный порядок чтения (логическая последовательность заголовков, правильное использование `<h1>`‑`<h6>`).
* Проверять валидность разметки с помощью линтеров (HTMLHint, axe) и тестировать доступность (Lighthouse, WAVE).
Что подразумевается под специфичностью в CSS?Специфичность — это система расчёта приоритетов селекторов, позволяющая определить, какие правила стилей переопределяют другие при конфликте. При вычислении учитывается тип селектора: inline‑стили (1000), ID‑селекторы (100), классы, атрибуты и псевдоклассы (10), типовые селекторы и псевдоэлементы (1). Суммируются все значения в порядке их появления в селекторе, образуя четырёхкомпонентный вектор (a,b,c,d), где a — флаг !important, b — inline, c — ID, d — классы/атрибуты/псевдоклассы, e — типы/псевдоэлементы. При сравнивании векторов более значимый компонент решает конфликт; если все равны, применяется правило, объявленное позже в коде. !important повышает приоритет, но всё равно подчиняется специфичности, если оба правила имеют !important. Наследование свойств происходит от родителя к потомку, однако более специфичное правило всегда переопределит унаследованное, независимо от порядка. Пример:
```css
/* специфичность 0‑0‑0‑1 */ p { color: red; }
/* специфичность 0‑0‑1‑0 */ .warning { color: orange; }
/* специфичность 0‑1‑0‑0 */ #header { color: blue; }
/* inline‑style (1000) */ <div style="color: green;"></div>
```
В результате элемент с id = header будет синим, даже если позже объявлен более “длинный” селектор, потому что 0‑1‑0‑0 > 0‑0‑1‑0 > 0‑0‑0‑1.
Важно использовать специфичность умеренно: избыточные ID‑селекторы усложняют поддержку, а !important следует применять только в исключительных случаях.
Для управления конфликтами рекомендуется придерживаться методологии (BEM, OOCSS) и держать специфичность на уровне 0‑0‑1‑0 или ниже.
---
Что подразумевается под ссылочными типами в программировании?Ссылочные типы — это типы данных, значения которых хранятся не непосредственно, а в виде ссылки (указателя) на объект в памяти; в JavaScript и TypeScript к ним относятся объекты, массивы, функции, даты, RegExp и любые пользовательские классы. При объявлении переменной ссылочного типа в стек помещается лишь ссылка, а сам объект размещается в куче. При присваивании одной переменной другой копируется сама ссылка, поэтому обе переменные указывают на один и тот же объект и изменения через одну переменную видны через другую. При передаче аргументов в функции происходит передача ссылки по значению, что позволяет функции мутировать объект‑аргумент. Операции сравнения ссылок проверяют, указывают ли они на один и тот же объект (===), а не эквивалентность содержимого. Для создания независимых копий требуется глубинное копирование (например, с помощью Object.assign, spread‑оператора или специализированных функций). Ссылочные типы отличаются от примитивных (number, string, boolean, null, undefined, symbol, bigint), которые хранят свои значения непосредственно и копируются при присваивании.
В чём различаются методы HTTP POST, PUT и PATCH?POST создаёт новый ресурс (или инициирует действие) и не идемпотентен; PUT полностью заменяет состояние ресурса по указанному URI и является идемпотентным; PATCH выполняет частичное обновление ресурса, передавая только изменяемые поля, и также считается идемпотентным при повторных одинаковых запросах. POST обычно используется для создания под‑коллекций (например, POST /clients для нового клиента), PUT — для замены/создания ресурса по известному пути (PUT /clients/123), PATCH — для изменения лишь отдельных атрибутов (PATCH /clients/123 {“email”: “new@example.com”}). Идемпотентность означает, что повторный запрос с теми же данными не меняет результат; POST этого свойства не гарантирует, в то время как PUT и PATCH при одинаковом теле запроса приводят к одинаковому состоянию ресурса. Кроме того, PUT обычно требует полной репрезентации ресурса, а PATCH — только изменений, что делает его более экономичным при работе с большими объектами.
В чём различаются атрибуты async и defer при загрузке скриптов в JavaScript?**Ответ:**
`async` и `defer` — это атрибуты `<script>`, влияющие на порядок загрузки и выполнения внешних скриптов. При `async` браузер начинает загрузку скрипта сразу, не блокируя парсинг HTML, и как только загрузка завершена, сразу же исполняет его, прерывая дальнейший парсинг. Это означает, что порядок выполнения async‑скриптов не гарантируется и может отличаться от их расположения в документе. `defer` также позволяет загружать скрипт параллельно с парсингом HTML, но отложит его выполнение до тех пор, пока весь документ не будет полностью разобран. При этом скрипты с `defer` сохраняют порядок их появления в разметке, выполняются последовательно после завершения парсинга и до события `DOMContentLoaded`. Оба атрибута работают только с внешними файлами (`src`), а не с инлайн‑скриптами. `async` полезен для небольших, независимых скриптов (например, аналитика), где порядок не важен. `defer` предпочтителен для скриптов, которые взаимодействуют с DOM и должны выполниться после полной загрузки страницы, но до `DOMContentLoaded`. Если атрибутов нет, скрипт блокирует парсинг и выполняется сразу после загрузки.
В чём отличается поверхностное (shallow) копирование от глубокого (deep) копирования?Поверхностное копирование создаёт новый объект, в котором копируются только ссылки на вложенные значения; если вложенные элементы являются объектами или массивами, обе копии будут указывать на один и тот же объект в памяти, поэтому изменения в вложенных структурах отражаются в обеих копиях. Глубокое копирование рекурсивно копирует все уровни вложенности, создавая полностью независимые копии всех вложенных объектов и массивов, так что изменения в любой части копии не влияют на оригинал. В JavaScript поверхностное копирование обычно реализуется с помощью `Object.assign`, оператора spread `{...obj}` или `Array.prototype.slice`/`Array.from`. Глубокое копирование можно выполнить вручную рекурсивным обходом, использовать `structuredClone` (современные браузеры), `JSON.parse(JSON.stringify(obj))` (с ограничениями: теряются функции, `Date`, `Map`, `Set` и т.п.), либо воспользоваться утилитами библиотек, например `_.cloneDeep` из Lodash. При выборе метода следует учитывать типы данных, требуемую производительность и необходимость сохранения специальных объектов (например, `Date`, `RegExp`, `Map`).
Для чего в фронтенде применяется управление состоянием?Управление состоянием (state management) необходимо для централизованного хранения, синхронизации и предсказуемого изменения данных, которые используются в разных частях пользовательского интерфейса. Оно решает проблемы «prop drilling», обеспечивает согласованность UI при асинхронных запросах, упрощает отладку благодаря единой точке правды и возможности отката/временного перематывания, облегчает тестирование и масштабирование приложения. Популярные подходы включают Redux, MobX, Vuex, Pinia, Context API, а также реактивные модели в Svelte или Solid. Выбор зависит от сложности проекта, требований к производительности и предпочтений команды.
Каким образом следует сравнивать объекты в JavaScript?Для сравнения объектов в JavaScript необходимо учитывать, что переменные, содержащие объекты, хранят ссылки. Оператор `===` (и `==`) проверяют только равенство ссылок, то есть два разных объекта с одинаковыми свойствами будут считаться неравными. Если требуется проверить, что два объекта имеют одинаковую структуру и значения, нужно выполнять глубокое сравнение. Возможные подходы:

- **Сравнение ссылок** – `obj1 === obj2`. Подходит, когда нужно узнать, указывают ли переменные на один и тот же объект.
- **Поверхностное сравнение** – проверка, что у обоих объектов одинаковый набор собственных перечислимых свойств и значения этих свойств сравниваются через `===`. Реализуется перебором `Object.keys`/`Object.entries` и проверкой длины массивов ключей.
- **Глубокое (deep) сравнение** – рекурсивное сравнение всех вложенных свойств, включая массивы, вложенные объекты, `Date`, `RegExp` и т.д. Можно написать собственную функцию или воспользоваться готовыми библиотеками: `_.isEqual` из Lodash, `deep-equal`, `fast-deep-equal`. При этом важно учитывать специальные случаи: `NaN` (сравнивается через `Object.is`), `+0` и `-0`, функции (обычно считаются неравными), циклические ссылки (нужно использовать `WeakMap` для отслеживания уже просмотренных объектов).

Пример собственного глубокого сравнения:

```js
function deepEqual(a, b, visited = new WeakMap()) {
if (Object.is(a, b)) return true; // одинаковые примитивы, включая NaN
if (typeof a !== 'object' || a === null ||
typeof b !== 'object' || b === null) return false;

if (visited.has(a) && visited.get(a) === b) return true;
visited.set(a, b);

const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;

for (const key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key], visited)) {
return false;
}
}
return true;
}
```

Альтернативный простой способ для объектов без функций и циклических ссылок – сравнение сериализованных представлений:

```js
JSON.stringify(obj1) === JSON.stringify(obj2);
```

Но этот метод чувствителен к порядку свойств и не работает с `undefined`, `function`, `Symbol`, `Date` и другими специальными типами.

Итого, выбор метода зависит от задачи: для проверки идентичности ссылки достаточно `===`; для поверхностного сравнения – перебор свойств; для полной эквивалентности структуры и значений – рекурсивное глубокое сравнение или проверенные библиотеки.
Какими способами можно определить, что переменная является массивом?Для проверки, что объект — массив, в JavaScript рекомендуется использовать `Array.isArray(value)`, так как он корректно работает во всех окружениях, включая разные окна/фреймы. Альтернативные методы: `value instanceof Array` (работает только в том же глобальном контексте), `Object.prototype.toString.call(value) === '[object Array]'` (универсальный, но более тяжёлый), проверка конструктора `value.constructor === Array` (может дать ложные результаты при переопределении). При необходимости проверки «псевдомассива» (например, `arguments` или `NodeList`) используют `Array.from` или проверку наличия свойства `length` и индексов. Наиболее надёжный и быстрый вариант — `Array.isArray`.
Как браузер визуализирует веб‑страницу?Отрисовка сайта в браузере начинается с загрузки ресурсов (HTML, CSS, JavaScript, изображения) через сетевой стек. Затем HTML‑парсер преобразует полученный HTML в DOM‑дерево, а CSS‑парсер — в CSSOM‑дерево. На их основе создаётся render tree (дерево рендеринга), которое содержит только видимые элементы со стилистической информацией. После этого происходит layout (расчёт геометрии всех узлов дерева рендеринга) и paint (рисование слоёв в битмапы). Полученные битмапы компонуются в окончательный кадр с помощью compositor и отображаются на экране. При изменениях DOM или CSS происходит пере‑расчёт (reflow) и пере‑рисовка (repaint). JavaScript, исполняемый в процессе, может модифицировать DOM/CSSOM, вызывая новые циклы отрисовки, а оптимизации браузера (GPU‑ускорение, слой‑кеширование) ускоряют процесс.
Какая задача из вашего опыта разработки фронтенда оказалась самой интересной?Одной из самых интересных задач было создание динамического редактора визуальных схем на чистом JavaScript с использованием Canvas и WebGL для рендеринга. Проект требовал реализации интерактивного перетаскивания узлов, автоматического расчёта маршрутов соединений с учётом препятствий, поддержки масштабирования и панорамирования, а также оптимизации отрисовки для поддержания 60 fps на слабых устройствах. Было использовано виртуальное дерево компонентов, паттерн observer для синхронизации состояния, а также техники дебаунса и requestAnimationFrame. Для ускорения вычислений маршрутов применялись алгоритмы поиска пути (A*), а для ускорения рендеринга — батчинг отрисовки и использование instanced drawing в WebGL. В итоге получился гибкий, расширяемый редактор, который успешно интегрировался в SaaS‑продукт, увеличив удержание пользователей на 30 %.
Какие способы применения хука useEffect ты знаешь?useEffect — это универсальный хук, позволяющий выполнять побочные эффекты в функциональных компонентах. Основные варианты использования: 1) запуск кода один раз при монтировании компонента (пустой массив зависимостей []); 2) запуск при изменении конкретных значений (массив зависимостей [dep1, dep2]); 3) очистка ресурсов через функцию‑return (unsubscribe, clearTimeout, abort controller); 4) несколько useEffect в одном компоненте для разделения логики (запрос данных, подписка на события, синхронизация с внешними API, работа с таймерами, анимациями, управлением фокусом и т. д.); 5) условный запуск внутри эффекта (if‑условия) или динамический список зависимостей; 6) создание кастомных хуков, инкапсулирующих повторяющиеся паттерны useEffect (например, useFetch, useInterval, useEventListener); 7) взаимодействие с рефами (useRef + useEffect) для доступа к DOM‑элементам после их рендера.
Какие особенности React ты знаешь?React — это библиотека для построения пользовательских интерфейсов, характеризующаяся компонентным подходом, использованием JSX, виртуального DOM, однонаправленного потока данных, хуков (useState, useEffect, useContext, useReducer, useMemo и др.), контекста, управления ошибками через Error Boundaries, поддержкой порталов, сервер‑сайд рендеринга (SSR) и статической генерации (SSG) через Next.js, механизмом Fiber для асинхронного рендеринга, Concurrent Mode, Suspense для отложенной загрузки данных, интеграцией с TypeScript, возможностью оптимизации через memo и lazy, а также обширным набором инструментов разработки (React DevTools, Profiler).
1) Перечислите, какие типы селекторов CSS вам известны.2) В CSS существует несколько групп селекторов:
• **Базовые селекторы** – тип (e.g., `div`), класс (`.menu`), идентификатор (`#header`), универсальный (`*`).
• **Комбинаторы** – дочерний (`>`), соседний (`+`), общий сосед (`~`), потомок (пробел).
• **Псевдоклассы** – состояния (`:hover`, `:focus`, `:active`), структуры (`:first-child`, `:last-of-type`, `:nth-child(2n+1)`, `:not(.disabled)`), формы (`:checked`, `:disabled`, `:valid`).
• **Псевдоэлементы** – части элементов (`::before`, `::after`, `::first-line`, `::first-letter`, `::selection`).
• **Атрибутные селекторы** – наличие (`[type]`), значение (`[type="text"]`), начало (`[href^="https"]`), конец (`[src$=".png"]`), содержимое (`[data-info*="admin"]`).
• **Функциональные селекторы** – `:is()`, `:where()`, `:has()`, `:matches()`, `:not()`, `:nth-child()`, `:nth-of-type()`.
• **Селекторы уровней** – `:root`, `:host`, `:host-context()`.
Каждый селектор имеет свою специфичность, которая определяется комбинацией типов (идентификаторы > классы/атрибуты/псевдоклассы > типы/псевдоэлементы). Правильное их комбинирование позволяет точно таргетировать нужные элементы без лишних переопределений.
3) ["CSS", "селекторы", "frontend", "веб-разработка"]
4) Junior, Middle
Какие способы реализации асинхронности ты знаешь?Асинхронность в frontend реализуется через несколько подходов:
1. **Коллбэки** – передача функции‑обработчика в асинхронный API (например, `setTimeout`, `XMLHttpRequest`).
2. **Promise** – объект, представляющий отложенный результат, с методами `then`, `catch`, `finally`. Позволяет цепочкой связывать асинхронные операции и избегать «callback hell».
3. **Async/Await** – синтаксический сахар над Promise, позволяющий писать асинхронный код в виде последовательных инструкций, используя `await` для ожидания результата.
4. **Event Loop и микротаски** – понимание очереди задач, микротасков (`Promise.then`, `process.nextTick`) и макротасков (`setTimeout`, `setInterval`, I/O) важно для управления порядком выполнения.
5. **Web Workers / Service Workers** – отдельные потоки выполнения, позволяющие выполнять тяжёлые вычисления или сетевые задачи без блокировки UI‑потока.
6. **Observable (RxJS, Most.js)** – потоковые последовательности данных, поддерживающие операторы трансформации, комбинирования и отмены подписки.
7. **Fetch API с потоковой передачой** – `fetch` возвращает Promise, а тело ответа можно читать как поток (`ReadableStream`), позволяя обрабатывать большие данные по частям.
8. **requestAnimationFrame** – планирование задач, синхронных с рендерингом кадра, используется для анимаций и обновления UI без задержек.
9. **Библиотеки и паттерны управления сайд‑эффектами** – Redux‑Thunk, Redux‑Saga, MobX‑async, которые инкапсулируют асинхронные действия в состоянии приложения.
10. **AbortController** – возможность отменять запросы (`fetch`, `XMLHttpRequest`) и другие асинхронные операции.
Каждый из этих методов имеет свои преимущества и ограничения, и часто их комбинируют для построения масштабируемых и отзывчивых интерфейсов.
Какими способами можно устранить проблему CORS?Для решения проблемы CORS можно использовать несколько подходов: 1️⃣ Настроить сервер‑бэкенд — добавить заголовок `Access-Control-Allow-Origin` со значением нужного домена или `*`, а при необходимости указать `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers` и `Access-Control-Allow-Credentials`. 2️⃣ Разместить промежуточный прокси‑сервер (reverse proxy) — клиент отправляет запросы к прокси того же origin, а прокси уже делает запрос к целевому API и возвращает ответ, скрывая CORS‑ограничения. 3️⃣ Использовать JSONP (только для GET‑запросов) — оборачивать ответ в вызов функции‑колбэка, позволяя обходить ограничения браузера. 4️⃣ Включить CORS‑middleware в популярных фреймворках (Express.js, Koa, Django, Spring и др.) для автоматического добавления нужных заголовков. 5️⃣ Для разработки можно временно отключить CORS в браузере (плагин, запуск Chrome с `--disable-web-security`) — но только в тестовой среде. 6️⃣ Если требуется аутентификация, использовать `Access-Control-Allow-Credentials: true` в сочетании с точным указанием origin (не `*`). 7️⃣ Настроить CDN или сервер статических файлов так, чтобы они обслуживали запросы с нужными CORS‑заголовками. Важно помнить, что правильное решение — настройка серверных заголовков, а остальные варианты подходят лишь как временные обходные решения.
Какие типы клиент‑сайд хранилищ доступны в браузере?Браузер предоставляет несколько механизмов для сохранения данных на стороне клиента: **cookies** (небольшие строки, отправляются с каждым запросом, ограничены по размеру ≈ 4 KB, могут иметь атрибуты HttpOnly, Secure, SameSite); **Web Storage** в виде **localStorage** (постоянное, синхронное, ≈ 5‑10 MB, доступно всем скриптам того же origin) и **sessionStorage** (жизненный цикл привязан к вкладке); **IndexedDB** (асинхронная NoSQL‑база, поддерживает большие объёмы ≈ hundreds MB, транзакции, индексы, хранит объекты, блобы, подходит для сложных структур); **Cache Storage** (часть Service Workers, хранит запрос‑ответ пары, используется для офлайн‑кеширования ресурсов); **WebSQL** (устаревший SQL‑подобный API, поддерживается только в старых версиях Chrome и Safari, не рекомендуется к использованию); **File System Access API** (позволяет работать с файлами и каталогами в изолированном пространстве, доступно по запросу у пользователя). Каждый из этих вариантов имеет свои ограничения по размеру, синхронности/асинхронности, безопасности и поддержке браузеров, поэтому выбор зависит от задачи: небольшие настройки → cookies/localStorage, временные данные → sessionStorage, большие структурированные данные → IndexedDB, кеширование статических ресурсов → Cache Storage.
В чём состоят преимущества и недостатки применения Redux в React‑приложениях?Redux обеспечивает предсказуемое управление состоянием за счёт единого хранилища, упрощает отладку (time‑travel, Redux DevTools), позволяет централизованно обрабатывать асинхронные операции через middleware (thunk, saga), улучшает тестируемость бизнес‑логики и облегчает масштабирование проекта благодаря чёткой архитектуре; однако внедрение Redux часто приводит к значительному объёму шаблонного кода, требует глубокого понимания концепций иммутабельности и потоков действий, может ухудшать производительность из‑за частых пересчётов и подписок, а в небольших приложениях добавляет избыточную сложность и увеличивает время разработки.
Какой уровень заработной платы ты ожидаешь?При ответе на вопрос о зарплатных ожиданиях важно подготовиться: изучить рыночные ставки для аналогичных позиций в вашем регионе и специализации (frontend‑разработчик), учитывать свой опыт, навыки (React, TypeScript, UI/UX), уровень ответственности и размер компании. Желательно назвать диапазон, а не конкретную цифру, например — от 80 000 до 120 000 рублей (или эквивалент в валюте), при этом подчеркнуть гибкость и готовность обсудить детали в зависимости от полного пакета (бонусы, удалёнка, обучение). Можно также уточнить, что ваша цель — найти позицию, где ценят профессиональный рост и возможности вклада, а не только фиксированную ставку.
Какова ожидаемая финансовая компенсация для фронтенд‑разработчика?Для фронтенд‑разработчиков финансовые ожидания зависят от уровня опыта, географии, компании и используемых технологий. В России junior‑уровень обычно получает 60 000–120 000 ₽ в месяц, middle — 120 000–250 000 ₽, senior — 250 000 ₽ и выше; в крупных международных компаниях и при работе удалённо зарплаты могут быть в долларах: junior — $2 000–$4 000, middle — $4 000–$8 000, senior — $8 000+. На доход влияют специализации (React, Angular, Vue), знание TypeScript, опыт в архитектуре приложений, участие в open‑source, а также бонусы, опционы, обучение и гибкий график. При обсуждении условий важно сравнить рыночные данные (Glassdoor, HH), учитывать стоимость жизни, уточнить состав компенсации (база, бонусы, equity) и готовность к росту в компании.
Какой у вас опыт в разработке серверной части приложений?Опыт backend разработки может включать: работу с серверными языками (Node.js, Python/Django, Java/Spring, PHP/Laravel), построение RESTful API и GraphQL‑сервисов, проектирование и оптимизацию баз данных (SQL – PostgreSQL, MySQL; NoSQL – MongoDB, Redis), реализацию аутентификации и авторизации (OAuth, JWT), написание unit‑ и интеграционных тестов, настройку CI/CD пайплайнов (GitHub Actions, GitLab CI), контейнеризацию приложений с Docker и оркестрацию в Kubernetes, масштабирование сервисов, мониторинг и логирование (Prometheus, Grafana, ELK). Также важны навыки работы с облачными сервисами (AWS, GCP, Azure), понимание микросервисной архитектуры, паттернов проектирования и обеспечения безопасности (OWASP).
В какой срок ты готов приступить к работе?Я могу выйти на работу через две недели после получения официального предложения, учитывая текущий отпуск и обязательный срок уведомления в моём нынешнем месте работы. При необходимости могу обсудить более ранний старт, если потребуется ускоренный процесс адаптации, а также готов рассмотреть варианты удалённого начала работы в течение первой недели. Если потребуется переезд, я готов организовать его в течение месяца, заранее согласовав детали с работодателем.
Разрабатывали ли вы собственные (кастомные) React‑хуки?Да, я создавал кастомные хуки для абстракции бизнес‑логики и повторно используемых эффектов: `useFetch` для запросов, `useForm` для управления состоянием формы, `useDebounce` для отложенного ввода, а также более сложные хуки, интегрирующие Redux‑состояние, WebSocket‑соединения и анимации с Framer Motion. При написании учитывал правила хуков (вызов только на верхнем уровне, отсутствие условных вызовов), типизацию через TypeScript, покрытие unit‑тестами с React Testing Library и Jest, а также документировал API хуков в Storybook. Использовал `useCallback`, `useMemo`, `useRef` и `useEffect` внутри хуков, обеспечивая чистый и предсказуемый жизненный цикл, а также предоставлял возможности кастомизации через параметры и обратные вызовы.
В чём различаются типы void, never, unknown и any?Тип **void** используется для указания, что функция не возвращает значимого значения; переменной типа void можно присвоить только undefined (и в строгом режиме только undefined). Тип **never** обозначает значение, которое никогда не возникает — функции, выбрасывающие исключение или бесконечно работающие, а также переменные, которые невозможно получить из любого другого типа (например, после бесконечного цикла). **unknown** — это безопасный аналог any: переменная может содержать значение любого типа, но перед её использованием требуется уточнение типа (type guard, проверка typeof и т.п.), иначе компилятор запрещает операции. **any** отключает проверку типов полностью: переменной любого типа можно присвоить значение any, а любые операции над ней допускаются без предупреждений, что удобно при миграции кода, но опасно, так как убирает статическую типизацию.

```text
// void
function log(message: string): void {
console.log(message);
}
let v: void = undefined; // OK
// v = 5; // Ошибка

// never
function fail(msg: string): never {
throw new Error(msg);
}
function endless(): never {
while (true) {}
}

// unknown
let u: unknown = "text";
u = 42;
if (typeof u === "string") {
console.log(u.toUpperCase()); // безопасно
}

// any
let a: any = 10;
a = "hello";
a.someNonExistentMethod(); // компилятор не ругается, но может бросить ошибку в рантайме
```
Какие факторы могут вызвать повторный рендеринг компонента?Повторный рендер компонента происходит, когда изменяются данные, от которых он зависит: 1) изменение локального state (через setState, useState, useReducer и т.п.); 2) изменение переданных ему props (включая изменение ссылок на функции/объекты); 3) рендеринг родительского компонента, приводящий к новому вызову дочернего; 4) изменение контекста (Context API) или подписка на внешние стои‑кi (Redux, MobX, Zustand и др.); 5) вызов методов принудительного обновления (forceUpdate, useForceUpdate); 6) изменения в хуках жизненного цикла (useEffect, useLayoutEffect) с изменёнными зависимостями; 7) изменение размеров окна или других глобальных событий, если они приводят к обновлению состояния; 8) изменение рефов, которые используются в вычислениях рендера; 9) применение memo‑изаций (React.memo, useMemo, useCallback) с изменёнными зависимостями, которые «снимают» оптимизацию и вызывают рендер. Все эти изменения приводят к тому, что React (или другой UI‑фреймворк) сравнивает новое виртуальное дерево с предыдущим и обновляет DOM.
Что такое Shadow DOM и как он обеспечивает инкапсуляцию компонентов?Shadow DOM — это часть спецификации Web Components, позволяющая создать изолированное дерево DOM, которое «прячется» за пределами основного документа. Оно обеспечивает инкапсуляцию разметки, стилей и поведения: CSS‑правила, объявленные внутри shadow‑корня, не влияют на внешний документ и не переопределяются внешними стилями, а элементы из внешнего DOM недоступны внутри shadow‑корня без явного экспорта (slot). Для создания используется метод `attachShadow({mode: 'open'|'closed'})` у любого элемента, после чего в полученный объект `ShadowRoot` добавляются HTML‑структура, CSS и JavaScript. В режиме `open` shadow‑корень доступен через свойство `element.shadowRoot`, в режиме `closed` — нет, что повышает безопасность. Shadow DOM часто комбинируется с кастомными элементами (`customElements.define`) и слотами (`<slot>`) для реализации переиспользуемых UI‑компонентов, таких как модальные окна, выпадающие меню, карточки и т.п., без риска конфликтов стилей или случайного вмешательства в DOM‑дерево страницы.
Что такое свойство box-sizing в CSS?Box-sizing — это CSS‑свойство, которое определяет, какие части элемента включаются в расчёт его ширины и высоты. По умолчанию (value `content-box`) в размер входят только содержимое, а отступы (padding) и границы (border) добавляются к указанным значениям width/height, что может усложнять расчёт layout. При значении `border-box` ширина и высота включают в себя padding и border, а содержимое автоматически подстраивается, что упрощает построение сеток и адаптивных макетов. Есть также устаревшее значение `padding-box`, которое учитывает padding, но не border. Применение `box-sizing: border-box;` к глобальному селектору `*` и псевдоклассу `*::before, *::after` часто рекомендуется для предсказуемого поведения размеров. Свойство наследуется, но обычно задаётся явно. Пример:

```css
*,
*::before,
*::after {
box-sizing: border-box;
}
```

Это делает все элементы и их псевдоэлементы включать padding и border в расчёт размеров, упрощая работу с flex‑ и grid‑контейнерами, медиазапросами и фиксированными ширинами. Поддерживается всеми современными браузерами, включая мобильные, и совместимо со старыми версиями IE8+ (при условии использования префикса `-ms-` в старых проектах).
Что делает метод preventDefault и в каких случаях его применяют?Метод preventDefault — это функция объекта события (Event) в браузерном API, вызываемая для отмены стандартного действия, которое браузер выполняет по умолчанию при возникновении данного события. Например, при клике на ссылку браузер переходит по её href, при отправке формы — происходит запрос, а при нажатии клавиши Enter в input может сработать автодополнение. Вызов event.preventDefault() инструктирует браузер не выполнять эти действия, позволяя разработчику полностью контролировать дальнейшую логику.
Типичный порядок использования:
```js
element.addEventListener('click', function (event) {
event.preventDefault(); // отменяем переход по ссылке
// собственный код: открытие модального окна, AJAX‑запрос и т.д.
});
```
Важно помнить, что preventDefault не останавливает распространение события по дереву DOM; для этого используют event.stopPropagation() или event.stopImmediatePropagation(). Кроме того, метод работает только для событий, у которых определено «cancellable» (можно отменить). Если попытаться вызвать preventDefault для несократимых событий, он не имеет эффекта и в консоли может появиться предупреждение. В современных фреймворках (React, Vue, Angular) часто используют синтетические события, но принцип остаётся тем же: вызвать e.preventDefault() в обработчике.
Метод широко применяется для:
- реализации кастомных форм и валидаций;
- создания SPA‑навигации без полной перезагрузки страницы;
- управления поведением клавиатурных и мышиных событий в интерактивных UI.
Что такое функция requestAnimationFrame и как она работает?requestAnimationFrame — это метод браузерного API, который позволяет синхронно с обновлением экрана планировать выполнение анимаций. Он принимает в качестве аргумента коллбек‑функцию, которая будет вызвана перед отрисовкой следующего кадра, обычно с частотой 60 кадров в секунду (или в соответствии с частотой обновления дисплея). В отличие от setTimeout/setInterval, requestAnimationFrame автоматически приостанавливается в неактивных вкладках, экономя ресурсы, и гарантирует, что изменения DOM и CSS будут выполнены в оптимальное время, уменьшая «тряску» и повышая плавность анимаций. Коллбек получает параметр timestamp — время в миллисекундах с начала навигации, что позволяет рассчитывать прогресс анимации. Для совместимости можно добавить полифилы, проверяя наличие window.requestAnimationFrame или используя префиксные варианты (webkitRequestAnimationFrame, mozRequestAnimationFrame). Пример использования:

```js
function animate(time) {
// расчёт позиции объекта
const progress = (time - start) / duration;
element.style.transform = `translateX(${progress * 100}%)`;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
let start = performance.now();
let duration = 2000; // 2 секунды
requestAnimationFrame(animate);
```

Таким образом, requestAnimationFrame — ключевой инструмент для создания высокопроизводительных и энергоэффективных анимаций в веб‑приложениях.
Что подразумевается под объектно‑ориентированным программированием (ООП)?Объектно‑ориентированное программирование (ООП) — парадигма разработки программ, в которой основными строительными блоками являются объекты — совокупности данных (состояния) и методов (поведения), объединённых в единую сущность. Ключевые концепции ООП: инкапсуляция (скрытие внутренней реализации и предоставление публичного интерфейса), наследование (создание новых классов на основе существующих, переиспользование кода), полиморфизм (возможность объектов разных классов обрабатывать одинаковые сообщения по‑разному) и абстракция (выделение существенных характеристик и игнорирование деталей). ООП способствует модульности, повторному использованию кода, упрощает поддержку и расширение систем, а также более естественно моделирует предметную область, представляя её в виде взаимосвязанных объектов.
Как определяется понятие полиморфизма?Полиморфизм — это способность объектов разных классов или типов реагировать на один и тот же вызов метода или функции по‑разному, реализуя собственную версию поведения. В контексте фронтенда полиморфизм проявляется в JavaScript и TypeScript через прототипное наследование, классы ES6 и интерфейсы: один и тот же метод, объявленный в базовом классе или интерфейсе, может быть переопределён в подклассах (runtime polymorphism, или динамический полиморфизм). Также существует параметрический полиморфизм, реализуемый обобщёнными типами в TypeScript (generic functions и классы), позволяющий писать функции, работающие с любыми типами, сохраняющими типовую информацию. Ад‑hoc полиморфизм реализуется через перегрузку функций (в TypeScript через объединения типов и сигнатуры) и через функции высшего порядка, принимающие разные типы аргументов. Примеры: компонент React, принимающий пропсы разных форм и рендерящий их через общий интерфейс; сервисы, реализующие один API, но имеющие разные реализации (REST, GraphQL); паттерн Strategy, где разные стратегии реализуют один метод `execute()`. Полиморфизм упрощает расширяемость кода, позволяет применять принципы SOLID (особенно OCP – открытость/закрытость) и писать более абстрактные, переиспользуемые модули.
Что подразумевается под псевдоэлементом?Псевдоэлемент — это специальный селектор в CSS, позволяющий стилизовать часть элемента, которую невозможно выделить обычным селектором, например первую букву, первую строку, вставку контента до или после элемента. Псевдоэлементы объявляются с двойным двоеточием `::` (в старой спецификации допускается один `:`), после имени селектора: `::before`, `::after`, `::first-line`, `::first-letter`, `::selection`, `::placeholder` и др. Они создают виртуальные дочерние элементы, к которым можно применять свойства оформления (цвет, шрифт, отступы, позиционирование, контент через `content`). Псевдоэлементы отличаются от псевдоклассов тем, что влияют на структуру DOM‑дерева, а не только на состояние элемента. Пример использования:

```css
p::first-letter {
font-size: 2em;
float: left;
margin-right: 0.1em;
}
button::after {
content: " →";
color: #888;
}
```

Псевдоэлементы поддерживаются всеми современными браузерами; однако некоторые (например, `::selection`) могут требовать префиксов `::-moz-selection`, `::-webkit-selection`. При комбинировании с псевдоклассами возможно писать цепочки вроде `a:hover::after`.

Важно помнить, что псевдоэлемент нельзя вложить в другой псевдоэлемент, а также что свойства `display`, `position`, `margin`, `padding` и `content` имеют особое значение для их рендеринга.
Зачем в React использовать свойства‑ключи?Ключи в React нужны для однозначной идентификации элементов списка при их рендеринге. Они позволяют алгоритму сравнения (reconciliation) эффективно определять, какие элементы изменились, добавились или удалились, что минимизирует количество операций обновления DOM и сохраняет состояние вложенных компонентов. Ключи должны быть уникальными среди соседних элементов и предпочтительно постоянными (например, ID из данных), а не индексом массива, чтобы избежать проблем при перестановке, вставке или удалении элементов. Правильное использование ключей повышает производительность и предсказуемость поведения UI.
Для чего используется хук useEffect в React?Хук useEffect позволяет выполнять побочные эффекты в функциональных компонентах — запросы к серверу, подписки, манипуляции с DOM, таймеры и т.п.; он имитирует методы жизненного цикла классов (componentDidMount, componentDidUpdate, componentWillUnmount), принимает функцию‑эффект и массив зависимостей, где пустой массив — выполнение только после монтирования, указанные зависимости — перезапуск при их изменении; функция‑эффект может возвращать функцию очистки, вызываемую перед следующим запуском или при размонтировании компонента; важно правильно указывать зависимости, использовать рефы для стабильных значений, избегать лишних ререндеров и помнить о совместимости с сервер‑сайд рендерингом.
Какие основные компоненты входят в Redux?Redux состоит из нескольких ключевых элементов: **Store** — центральное хранилище состояния приложения; **Actions** — обычные объекты, описывающие события, которые могут изменить состояние; **Reducers** — чистые функции, принимающие текущее состояние и действие и возвращающие новое состояние; **Middleware** — промежуточный слой, позволяющий перехватывать и обрабатывать действия (например, redux‑thunk, redux‑saga); **Selectors** — функции для извлечения и преобразования данных из Store; а также **Store enhancers** и **combineReducers** для расширения функциональности и объединения нескольких редьюсеров.
Как можно скопировать (клонировать) объект в JavaScript?Для клонирования объекта в JavaScript существует несколько подходов, каждый из которых подходит под определённые требования к глубине копии и типам вложенных данных:

**Поверхностное (shallow) копирование**
- `Object.assign({}, source)` – копирует перечислимые собственные свойства первого уровня, ссылки на вложенные объекты сохраняются.
- Оператор распространения (spread) `{...source}` – синтаксический сахар над `Object.assign`, работает аналогично.

**Глубокое (deep) копирование**
- `JSON.parse(JSON.stringify(source))` – простейший способ получить полностью независимую копию, но теряет функции, `Date`, `RegExp`, `Map`, `Set`, `undefined`, `Symbol` и не поддерживает циклические ссылки.
- `structuredClone(source)` (встроенная функция в современных браузерах и Node.js) – делает глубокую копию, сохраняет типы данных, поддерживает `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, а также корректно обрабатывает циклические ссылки; не копирует функции.
- Рекурсивный пользовательский клон: реализовать функцию, проходящую по всем свойствам, проверяющую тип (`Array.isArray`, `instanceof Date`, `instanceof RegExp`, `Map`, `Set` и т.д.), создающую соответствующие копии и поддерживающую обнаружение и обработку циклических ссылок через `WeakMap`. Пример базовой реализации:

```js
function deepClone(value, weakMap = new WeakMap()) {
if (value === null || typeof value !== 'object') return value;
if (weakMap.has(value)) return weakMap.get(value);

let clone;
if (Array.isArray(value)) clone = [];
else if (value instanceof Date) clone = new Date(value);
else if (value instanceof RegExp) clone = new RegExp(value);
else if (value instanceof Map) {
clone = new Map();
value.forEach((v, k) => clone.set(k, deepClone(v, weakMap)));
return clone;
} else if (value instanceof Set) {
clone = new Set();
value.forEach(v => clone.add(deepClone(v, weakMap)));
return clone;
} else {
clone = Object.create(Object.getPrototypeOf(value));
}

weakMap.set(value, clone);
Reflect.ownKeys(value).forEach(key => {
clone[key] = deepClone(value[key], weakMap);
});
return clone;
}
```

**Клонирование с сохранением функций**
- Функции нельзя сериализовать через `JSON`, но их можно копировать как ссылки (shallow) или воссоздавать с помощью `new Function` при необходимости, однако это редко требуется.

**Библиотеки**
- `lodash.cloneDeep(value)` – проверенный способ глубокого копирования, учитывающий большинство встроенных типов.
- `ramda.clone`, `rfdc` (Really Fast Deep Clone) – лёгкие альтернативы с высокой производительностью.

**Выбор подхода** зависит от:
1. Требуемой глубины копии (shallow vs deep).
2. Наличия специальных типов (Date, RegExp, Map/Set, Buffer).
3. Нужды в поддержке циклических ссылок.
4. Производительности и размера bundle (встроенные методы vs сторонние библиотеки).

Для большинства современных проектов рекомендуется использовать `structuredClone`, если нужна глубокая копия без сторонних зависимостей; для более старых окружений – `JSON.parse(JSON.stringify(...))` (с учётом ограничений) или `lodash.cloneDeep`.
Каким образом можно организовать процесс CI/CD для проекта фронтенда?Для настройки CI/CD в фронтенд‑проекте необходимо выполнить следующие шаги: 1) выбрать систему автоматизации (GitHub Actions, GitLab CI, CircleCI, Jenkins и др.) и подключить репозиторий; 2) создать конфигурационный файл pipeline (например, .github/workflows/ci.yml), где описать последовательность задач — установку Node.js и зависимостей (npm ci), запуск линтинга (npm run lint), выполнение юнит‑ и e2e‑тестов (npm test, npm run e2e), сборку артефактов (npm run build); 3) настроить кэширование node_modules и сборки для ускорения; 4) добавить шаги деплоя в выбранную среду (Netlify, Vercel, Firebase, AWS S3, GitHub Pages) с использованием секретов и переменных окружения; 5) настроить ветвление: запуск pipeline только для pull‑request в main/master, отдельные сборки для feature‑веток; 6) включить уведомления в Slack/Telegram и проверку качества кода (code coverage, SonarQube); 7) при необходимости использовать Docker‑контейнеры для единообразия среды; 8) реализовать стратегию отката (rollback) через версии артефактов или blue‑green деплой. После коммита в репозиторий система автоматически выполнит все описанные этапы, гарантируя, что каждый релиз проходит проверку и безопасно выкладывается в продакшн.