Вопросы по JavaScript Core (Event Loop, замыкания, асинхронность), TypeScript и React (Hooks, рендеринг, оптимизация).
JavaScript является однопоточным языком выполнения. Event Loop координирует выполнение кода, обработку событий и работу асинхронных колбэков. 1. **Call Stack (Стек вызовов)**: Выполняет синхронный код по очереди. 2. **Web APIs (в браузере)**: Фоновые операции (таймауты, сетевые запросы fetch, обработчики событий DOM) выполняются средой браузера параллельно. 3. **Очереди задач**: * **Microtask Queue (Очередь микрозадач)**: Хранит колбэки промисов (`then/catch/finally`, `async/await`, `queueMicrotask`). Имеет высший приоритет. * **Macrotask Queue (Очередь макрозадач)**: Хранит `setTimeout`, `setInterval`, события ввода-вывода, рендеринг. 4. **Алгоритм**: Когда Call Stack пуст, Event Loop выгребает **все** задачи из очереди микрозадач и выполняет их. Затем он берет **одну** макрозадачу из очереди макрозадач, выполняет ее, и цикл повторяется.
Замыкание — это способность функции запоминать свою лексическую область видимости (лексическое окружение, `Lexical Environment`) и иметь доступ к переменным из родительской области видимости даже после того, как родительская функция завершила свою работу. **Пример применения**: * Инкапсуляция данных (создание приватных переменных). * Каррирование и частичное применение функций. * Сохранение состояния в колбэках.
* **`var`**: Старый стандарт. Имеет функциональную область видимости (игнорирует блоки `{}`). Поддерживает поднятие (hoisting) — переменная создается до выполнения кода и инициализируется как `undefined`. Допускает повторное объявление. * **`let`**: Блочная область видимости. Находится во временной мертвой зоне (Temporal Dead Zone) до своей инициализации в коде (обращение до объявления вызовет ошибку). Не допускает повторное объявление. * **`const`**: Блочная область видимости. Требует обязательной инициализации при объявлении. Ссылку на объект изменить нельзя, но свойства самого объекта изменять можно (объект мутабелен).
Промис представляет собой результат асинхронной операции, находящийся в одном из трех состояний: `pending` (ожидание), `fulfilled` (успешно) или `rejected` (ошибка). **Комбинаторы промисов**: * **`Promise.all`**: Ждет завершения всех промисов. Если хотя бы один падает с ошибкой — вся цепочка немедленно падает с этой ошибкой. * **`Promise.allSettled`**: Ждет завершения всех промисов (успешных и провальных) и возвращает массив объектов с их статусами и результатами. * **`Promise.race`**: Возвращает результат первого завершившегося промиса (неважно, успешно или с ошибкой). * **`Promise.any`**: Возвращает первый **успешно** завершившийся промис. Падает только если упали все промисы.
Объекты в JavaScript наследуют свойства и методы друг друга через скрытую ссылку на прототип. * **`__proto__`**: Геттер/сеттер на объекте, указывающий на его реальный прототип в памяти (ссылка на экземпляр родительского прототипа). * **`prototype`**: Свойство функции-конструктора (или класса). Указывает, какой объект будет записан в качестве `__proto__` для всех новых объектов, созданных через `new` этой функцией. * **Цепочка прототипов**: При обращении к свойству объекта JS ищет его в самом объекте. Если не находит — ищет в его `__proto__`, затем в `__proto__` его прототипа и т.д., пока не достигнет `Object.prototype.__proto__`, который равен `nil` (`null`).
* **`this`**: У стрелочных функций нет своего контекста `this`. Они наследуют `this` лексически из окружающего контекста на момент создания. У обычных функций `this` определяется динамически в момент вызова метода. * **`arguments`**: Стрелочные функции не имеют локального объекта `arguments` (нужно использовать rest-параметры `...args`). * **Конструктор**: Стрелочные функции нельзя вызывать с ключевым словом `new` (у них нет свойства `prototype`), они не могут быть генераторами.
Virtual DOM — это легковесная копия реального DOM дерева в виде JS-объектов в оперативной памяти. * **Процесс сверки (Diffing Algorithm)**: 1. При изменении состояния компонентов React строит новое Virtual DOM дерево. 2. Сравнивает его с предыдущим деревом с помощью эвристического алгоритма за `O(N)`. 3. Выявляет минимальный набор изменений (патчи). 4. Синхронизирует изменения с реальным DOM (процесс Commit). * **Эвристики**: Два элемента разных типов создают разные деревья; списки элементов требуют уникальных ключей `key` для отслеживания перемещений без полной перерисовки.
Атрибут `key` помогает алгоритму сверки React определять, какие элементы списка были изменены, добавлены или удалены. Без уникальных ключей React перерисовывает все элементы списка подряд (что снижает производительность и сбрасывает локальные состояния компонентов вроде фокуса или ввода). Использовать индексы массива в качестве `key` не рекомендуется при изменении порядка элементов.
Хуки позволяют использовать состояние и другие возможности React без написания классов. * **Правила хуков**: 1. Вызывать хуки можно только на самом верхнем уровне функций React. Нельзя вызывать их внутри циклов, условий `if` или вложенных функций (React полагается на строгий порядок вызова хуков при каждом рендере). 2. Вызывать хуки можно только из функциональных компонентов React или кастомных хуков.
* **`useEffect`**: Выполняется **асинхронно после** того, как браузер отрисовал изменения на экране (не блокирует рендеринг). Идеален для сетевых запросов, подписок. * **`useLayoutEffect`**: Выполняется **синхронно после** мутации DOM, но **до** того, как браузер отрисует пиксели на экране. Используется для измерения размеров элементов и предотвращения визуальных мерцаний UI. * **`useInsertionEffect`**: Выполняется **до** мутации DOM. Предназначен исключительно для библиотек CSS-in-JS (например, styled-components) для динамического внедрения стилей.
* **`React.memo`**: Компонент высшего порядка (HOC). Предотвращает перерисовку компонента, если его пропсы не изменились (делает поверхностное сравнение пропсов). * **`useMemo`**: Кэширует результат ресурсоемких вычислений между рендерами. * **`useCallback`**: Кэширует ссылку на саму функцию между рендерами, предотвращая пересоздание обработчиков событий и лишние перерисовки дочерних компонентов, обернутых в `React.memo`.
Context API позволяет передавать данные сквозь дерево компонентов без ручной передачи пропсов на каждом уровне (prop drilling). * **Минус**: Любое изменение значения в `Context.Provider` приводит к **принудительной перерисовке всех** компонентов-потребителей этого контекста (`useContext`), даже если они используют только часть данных из него. Для часто меняющихся состояний рекомендуется использовать стейт-менеджеры (Zustand, Redux).
* **Объединение (Declaration Merging)**: Несколько объявлений `interface` с одинаковым именем автоматически объединяются в один. `type` не поддерживает слияние. * **Синтаксические возможности**: `type` более гибок. Он позволяет создавать типы-перечисления через `union` (`type A = B | C`), работать с примитивами, кортежами (tuples) и маппингом типов. `interface` описывает исключительно структуру объектов.
Type Guards позволяют сузить тип переменной внутри блока кода. * **Встроенные**: `typeof x === "string"`, `x instanceof Class`. * **User-defined**: Функция, возвращающая предикат типа (`x is Type`). * `fun isString(val: any): val is string { return typeof val === "string"; }` Если функция возвращает `true`, компилятор считает, что внутри блока `if` переменная имеет тип `string`.
Встроенные вспомогательные дженерики для трансформации типов: * **`Partial<T>`**: Делает все свойства типа `T` необязательными. * **`Pick<T, K>`**: Создает тип, выбирая только перечисленные ключи `K` из типа `T`. * **`Omit<T, K>`**: Создает тип, исключая ключи `K` из типа `T`. * **`Record<K, T>`**: Создает тип объекта со свойствами типа `K` и значениями типа `T`.
* **Классовые компоненты**: * Монтирование: `constructor` -> `render` -> `componentDidMount`. * Обновление: `shouldComponentUpdate` -> `render` -> `componentDidUpdate`. * Размонтирование: `componentWillUnmount`. * **Функциональные компоненты**: Жизненный цикл эмулируется хуком `useEffect`. Массив зависимостей определяет момент вызова, а возвращаемая функция-очистка (cleanup) выполняет роль `componentWillUnmount`.
* **SSR (Рендеринг на сервере)**: HTML генерируется сервером под каждый входящий HTTP-запрос от пользователя. Подходит для динамических страниц с частым изменением данных. * **SSG (Статическая генерация)**: HTML генерируется один раз на этапе сборки проекта (build time) и отдается пользователям как готовые статические файлы. Обеспечивает максимальную скорость загрузки.
Гидратация — это процесс на стороне клиента, при котором React принимает статический HTML-код, сгенерированный сервером (SSR), «оживляет» его, навешивая слушатели событий и инициализируя внутренние состояния компонентов, делая страницу интерактивной.
* **`GET`**: Безопасный, идемпотентный метод получения данных. * **`POST`**: Неидемпотентный метод создания нового ресурса. * **`PUT`**: Идемпотентный метод полной замены существующего ресурса новым телом запроса. * **`PATCH`**: Идемпотентный метод частичного изменения ресурса. * **`DELETE`**: Идемпотентный метод удаления ресурса.
CORS — это механизм безопасности браузеров, запрещающий веб-странице делать запросы к другому домену (origin), отличному от того, с которого она была загружена. Для разрешения таких запросов сервер должен возвращать специальные HTTP-заголовки, такие как `Access-Control-Allow-Origin: *` или адрес конкретного доверенного домена.
* **`call()`**: Вызывает функцию с явно указанным контекстом `this` и аргументами, переданными через запятую. * **`apply()`**: Вызывает функцию с явно указанным контекстом `this` и аргументами, переданными в виде массива. * **`bind()`**: Не вызывает функцию немедленно. Возвращает новую копию функции с жестко привязанным контекстом `this` для последующих вызовов.
Генераторы — это функции, выполнение которых можно приостанавливать и возобновлять. Объявляются со звездочкой `function*` и используют оператор `yield` для возврата промежуточных значений во время итераций.
Shadow DOM — это технология веб-компонентов, позволяющая инкапсулировать разметку и стили элемента внутри изолированного поддерева DOM, скрывая их от глобальных стилей CSS страницы и скриптов.
* В `WeakMap` ключами могут быть исключительно объекты, а в `WeakSet` — только объекты. * **Сборка мусора**: Ссылки на объекты-ключи в WeakMap являются слабыми. Если на объект-ключ больше нет сильных ссылок в программе, он автоматически удаляется сборщиком мусора вместе со значением из WeakMap, предотвращая утечки памяти.
* **`localStorage`**: Хранит данные бессрочно в браузере. Объем — до 5–10 МБ. Данные не отправляются на сервер автоматически. * **`sessionStorage`**: Данные удаляются при закрытии вкладки браузера. Объем — до 5 МБ. * **`Cookies`**: Хранят небольшие объемы данных (до 4 КБ). Автоматически прикрепляются браузером к каждому HTTP-запросу к домену. Имеют флаги безопасности (`HttpOnly`, `Secure`).
Строгий режим (`"use-mode"`) активирует строгие проверки кода: * Запрещает создание глобальных переменных без объявления. * Выбрасывает исключения при попытке записи в свойства, доступные только для чтения. * Устанавливает контекст `this` равным `undefined` в обычных функциях, вызванных без контекста.
Деструктуризация — это синтаксис, позволяющий извлекать значения из массивов или свойства из объектов напрямую в отдельные переменные в одну строку кода (например, `const { name, age } = user;`).
Это последовательность шагов, выполняемая браузером для преобразования HTML, CSS и JS в реальные пиксели на экране: HTML -> DOM, CSS -> CSSOM, DOM + CSSOM -> Render Tree -> Layout (расчет позиций) -> Paint (отрисовка) -> Composite (композиция слоев).
Дженерики позволяют создавать переиспользовать типы данных, работая с абстрактными типами (плейсхолдерами), конкретизируемыми в момент использования функции, класса или интерфейса (например, `Array<T>`).
* **Debounce (Устранение дребезга)**: Откладывает вызов функции до тех пор, пока не пройдет заданное время молчания после последнего действия (полезно для автопоиска при вводе). * **Throttle (Дросселирование)**: Ограничивает частоту вызовов функции, гарантируя, что она будет выполняться не чаще одного раза в заданный интервал времени (полезно для обработчиков скролла или изменения размера экрана).
JavaScript однопоточен. Event Loop координирует выполнение кода, событий и задач: 1. **Call Stack (Стек вызовов)**: Выполняет синхронный код. 2. **Web APIs**: Браузерные API (таймеры, сетевые запросы) выполняются в фоне. 3. **Очереди задач**: * **Microtask Queue (Очередь микрозадач)**: Сюда попадают колбэки промисов (`.then`, `catch`, `finally`, `await`), `queueMicrotask` и `MutationObserver`. Выполняется полностью сразу после очистки Call Stack, перед перерисовкой экрана. * **Macrotask Queue (Очередь макрозадач)**: Сюда попадают `setTimeout`, `setInterval`, события ввода-вывода. Выполняются по одной задаче за итерацию цикла.
* **`var`**: Имеет функциональную область видимости (function scope), подвержен поднятию (hoisting) с инициализацией в `undefined`. Можно переопределять и переобъявлять. * **`let`**: Имеет блочную область видимости (block scope). Подвержен hoisting, но находится во временной мертвой зоне (TDZ) до объявления. Нельзя переобъявлять в той же области. * **`const`**: Аналогичен `let` по области видимости, но требует обязательной инициализации при объявлении и запрещает переопределение ссылки на значение (при этом свойства объектов внутри const изменять можно).
Замыкание — это способность функции запоминать свою лексическую область видимости и иметь доступ к переменным из родительской области видимости даже после того, как родительская функция завершила свое выполнение. **Применение**: * Создание приватных переменных и методов (инкапсуляция). * Каррирование и частичное применение функций. * Сохранение состояния в фабричных функциях.
Каждый объект в JavaScript имеет скрытую ссылку на другой объект, называемый его прототипом. При обращении к свойству объекта, если оно не найдено в самом объекте, поиск продолжается в его прототипе, затем в прототипе прототипа и так далее по **цепочке прототипов (prototype chain)** до тех пор, пока не будет найдено совпадение или цепочка не закончится на `null`. * `__proto__` — геттер/сеттер для доступа к прототипу существующего объекта. * `prototype` — свойство функции-конструктора, которое будет назначено прототипом (`__proto__`) для всех объектов, созданных через `new`.
Все три метода используются для явной привязки контекста (`this`) к функции: * **`call`**: Вызывает функцию немедленно с указанным контекстом и аргументами, переданными через запятую: `func.call(context, arg1, arg2)`. * **`apply`**: Вызывает функцию немедленно с указанным контекстом, но аргументы передаются в виде массива: `func.apply(context, [arg1, arg2])`. * **`bind`**: Не вызывает функцию сразу. Возвращает новую функцию с жестко привязанным контекстом, которую можно вызвать позже.
* **`Promise.all`**: Ждет выполнения всех промисов. Если хотя бы один завершился с ошибкой, весь вызов падает с этой ошибкой. * **`Promise.allSettled`**: Ждет завершения всех промисов (успешного или ошибочного). Возвращает массив объектов со статусами и результатами. * **`Promise.any`**: Ждет выполнения первого *успешного* промиса. Падает, только если упали все промисы. * **`Promise.race`**: Возвращает результат первого завершившегося промиса (успех или ошибка).
React строит виртуальное дерево DOM (Virtual DOM) в памяти. При изменении состояния создается новое виртуальное дерево, и React сравнивает его со старым с помощью эвристического алгоритма **Diffing**: 1. Если изменился тип элемента (например, с `div` на `span`), React полностью уничтожает старое дерево и строит новое с нуля. 2. При сравнении списков React использует атрибут `key`. Ключи помогают React определить, какие элементы были перемещены, добавлены или удалены, избегая лишней перерисовки неизмененных элементов.
В классовых компонентах жизненный цикл делится на фазы монтирования, обновления и размонтирования (`componentDidMount`, `componentDidUpdate`, `componentWillUnmount`). В функциональных компонентах вся эта логика объединена в хуке `useEffect`: * Пустой массив зависимостей `[]` аналогичен `componentDidMount`. * Зависимости в массиве `[prop]` аналогичны отслеживанию изменений в `componentDidUpdate`. * Функция очистки (return callback) аналогична `componentWillUnmount`.
Функция очистки запускается перед выполнением эффекта с новыми зависимостями и при размонтировании компонента. Она необходима для предотвращения утечек памяти и побочных эффектов. В ней нужно отменять подписки на события (`removeEventListener`), закрывать соединения WebSocket, сбрасывать таймеры (`clearTimeout`/`clearInterval`) и отменять активные сетевые запросы.
* **`useMemo`**: Кэширует (мемоизирует) **результат вычисления** функции между рендерами. Полезно для тяжелых расчетов. * **`useCallback`**: Кэширует **саму функцию** (ссылку на нее). Полезно для передачи колбэков в дочерние компоненты, оптимизированные с помощью `React.memo`, чтобы предотвратить их лишний рендеринг из-за изменения ссылок на функции. * **Когда не нужно**: Для простых операций мемоизация не имеет смысла, так как накладные расходы на работу самих хуков превышают пользу от оптимизации.
Контекст `useContext` не является полноценным менеджером состояния. Его основная проблема заключается в том, что при изменении значения в провайдере контекста **все** компоненты, которые подписаны на этот контекст (вызывают `useContext`), будут принудительно перерендерены, даже если они используют совершенно другие поля из объекта контекста. Для решения проблемы контекст разделяют на несколько мелких или переходят на стейт-менеджеры (Zustand, Redux).
* **`useState`**: Хранит состояние, изменение которого автоматически вызывает **перерисовку (ререндер)** компонента. * **`useRef`**: Хранит мутабельное значение в свойстве `.current`, изменение которого **не вызывает** ререндер компонента. Используется для хранения ссылок на реальные DOM-элементы или сохранения значений (например, ID таймеров), которые должны пережить ререндеры без перерисовки UI.
* **Flux/Redux**: Однонаправленный поток данных. Глобальный неизменяемый Store, изменения только через Actions и Reducers. Надежно, но требует много шаблонного кода (boilerplate). * **Atomic (Recoil, Jotai)**: Состояние собирается из независимых мелких атомов. * **Proxy-based (MobX, Valtio)**: Состояние реактивно. Изменения происходят мутацией объекта, прокси отслеживают это и обновляют UI. * **Simple Store (Zustand)**: Легковесный менеджер состояния на основе хуков, быстрый в настройке и без лишнего шаблонного кода.
Дженерики (Generics) — это параметры типов, которые позволяют создавать обобщенные классы, интерфейсы и функции, работающие с различными типами данных, сохраняя при этом строгую типизацию (type safety). ```typescript function identity<T>(arg: T): T { return arg; } let result = identity<string>("hello"); ``` Ограничения типов (`extends`) позволяют сузить спектр принимаемых типов (например, `<T extends { id: number }>`).
* **`interface`**: Предназначен для описания структуры объектов. Поддерживает **слияние деклараций (declaration merging)** — если объявить два интерфейса с одинаковым именем, они объединятся. Расширяется через `extends`. * **`type`**: Псевдоним типа. Более гибок: может описывать примитивы, объединения (`union: A | B`), пересечения (`intersection: A & B`) и кортежи. Не поддерживает слияние деклараций.
* **`Partial<T>`**: Делает все свойства типа `T` необязательными (добавляет `?`). * **`Pick<T, K>`**: Создает новый тип, выбирая только указанные ключи `K` из типа `T`. * **`Omit<T, K>`**: Создает тип, исключая указанные ключи `K` из типа `T`. * **`Record<K, T>`**: Создает тип объекта с ключами типа `K` и значениями типа `T`.
* Использовать стабильные и уникальные значения в качестве `key` (избегать индексов массива, если порядок элементов может меняться). * Использовать **виртуализацию списков** (библиотеки вроде `react-window` или `react-virtualized`) для рендеринга только тех элементов списка, которые видны на экране в данный момент. * Обернуть компоненты строк списка в `React.memo` для предотвращения их ререндера при изменении других элементов.
* **Webpack**: Строит полный бандл всего приложения в памяти перед запуском сервера разработки, что делает старт долгим на больших проектах. * **Vite**: В режиме разработки не строит бандл. Он использует нативные ES-модули (`ESM`) браузера. Браузер сам запрашивает нужные файлы, а Vite быстро компилирует их на лету с помощью сверхбыстрого сборщика `esbuild` (написанного на Go). Это дает мгновенный старт сервера и горячую перезагрузку (HMR).
* **SSG (Static Site Generation)**: Страницы генерируются в HTML один раз на этапе сборки проекта (build time). Страницы загружаются мгновенно и отлично индексируются поисковиками. Подходит для блогов, документации. * **SSR (Server-Side Rendering)**: HTML генерируется сервером при каждом входящем запросе от пользователя. Позволяет отображать всегда актуальные персонализированные данные, но создает нагрузку на сервер и увеличивает TTFB (Time to First Byte).
CORS (Cross-Origin Resource Sharing) — политика безопасности браузеров, запрещающая веб-странице делать запросы к другому домену (origin), отличному от того, с которого она была загружена. **Решения**: 1. Настроить сервер, к которому идет запрос, добавив заголовок `Access-Control-Allow-Origin: *` (или разрешить конкретный домен). 2. Использовать проксирование запросов через свой сервер разработки (например, настроить proxy в Vite/Webpack, чтобы запросы шли на один домен, а прокси перенаправлял их на бэкенд).