Раздел подготовки

JavaScript/TypeScript: вопросы для собеседования QA

JS/TS важны для QA, который работает с вебом, автотестами, Playwright/Cypress или просто должен понимать поведение фронтенда.

75 вопросов 17 базовых 3 уровня: Junior, Middle, Senior

Что закрывает этот раздел

Подготовиться к JS/TS вопросам для QA, Fullstack QA и AQA роли.

  • понимать async-код
  • читать типизированный код
  • объяснять ошибки фронтенда

С чего начать

Если времени мало, начни с базовых вопросов: они чаще всего помогают пройти первый технический фильтр и показать структуру мышления.

  1. Чем отличается let от var в JavaScript?
  2. Что такое поднятие (hoisting) в JavaScript и как оно работает?
  3. В чём разница между == и === в JavaScript?
  4. Какие примитивные типы данных есть в JavaScript?
  5. Чем отличается null от undefined?
  6. Какие значения в JavaScript считаются ложными (falsy)?
  7. Что такое замыкание (closure) и приведите пример его использования.
  8. Чем стрелочные функции отличаются от обычных?

Вопросы и ответы

Чем отличается let от var в JavaScript?

Junior

let имеет блочную область видимости — переменная живёт только внутри ближайших фигурных скобок. var имеет функциональную область — переменная видна во всей функции, где объявлена.

При попытке обратиться к let до его объявления вы получите ReferenceError (зона временной мёртвой зоны). var в этом случае вернёт undefined из-за поднятия (hoisting).

Из практики: для нового кода всегда используйте let и const. var оставлен только для совместимости со старыми проектами.

Частые ошибки

  • Считать, что let поднимается так же, как var.
  • Объявлять var внутри цикла и удивляться, что замыкание видит последнее значение.

Дополнительные вопросы

  • Что выведет console.log(x); var x = 5;?
  • Чем const отличается от let?

Что такое поднятие (hoisting) в JavaScript и как оно работает?

Junior

Поднятие — это поведение интерпретатора: объявления переменных и функций как бы «всплывают» в начало своей области видимости ещё до выполнения кода.

var поднимается и инициализируется значением undefined. let и const тоже поднимаются, но не инициализируются — обращение к ним до объявления даёт ReferenceError. Функциональные объявления поднимаются целиком (можно вызвать до строки с function ...).

Из практики: на собеседовании часто просят прокомментировать вывод кода с обращением к переменной до её объявления — нужно различать var, let и function declaration.

Частые ошибки

  • Путать поднятие объявления и поднятие присваивания: значение не переносится.
  • Считать, что function expression (const f = function...) можно вызвать до объявления.

Дополнительные вопросы

  • Какая разница между function declaration и function expression?
  • Что такое временная мёртвая зона (TDZ)?

В чём разница между == и === в JavaScript?

Junior

== — нестрогое сравнение, оно приводит операнды к одному типу перед сравнением. === — строгое сравнение, оно сравнивает и значение, и тип, без приведения.

Например, 0 == '' вернёт true, потому что строка преобразуется в число. А 0 === '' вернёт false — разные типы.

Из практики: используйте === по умолчанию. Это убирает целый класс багов с неявным приведением типов и читается прозрачнее.

Частые ошибки

  • Сравнивать null и undefined через ==, потом удивляться, что они равны друг другу, но не другим falsy.
  • Использовать == с числами и строками — приведение даёт неожиданные результаты.

Дополнительные вопросы

  • Что вернёт [] == false?
  • Когда === ведёт себя не так, как ожидалось? (NaN === NaN)

Какие примитивные типы данных есть в JavaScript?

Junior

Примитивов семь: string, number, bigint, boolean, undefined, null и symbol. Всё остальное — объекты (включая массивы, функции, даты).

Примитивы передаются по значению — при присваивании копируется содержимое. Объекты передаются по ссылке.

Из практики: знание примитивов помогает понимать, почему a = b; b = 5; не меняет a для числа, но меняет для объекта.

Частые ошибки

  • Считать массив или функцию примитивом.
  • Забывать про bigint — он отдельный тип, не совместимый с обычным number в арифметике.

Дополнительные вопросы

  • Чем typeof null отличается от typeof undefined?
  • Что выведет typeof []?

Чем отличается null от undefined?

Junior

undefined — это значение по умолчанию у необъявленной или не присвоенной переменной, а также у отсутствующего свойства объекта. JavaScript сам присваивает undefined.

null — это значение, которое разработчик ставит явно, чтобы сказать «здесь намеренно пусто». Например, заранее объявить переменную, в которую позже придёт объект.

Из практики: при сравнении JSON-ответов API обычно null приходит с сервера, а undefined — это сигнал «поле не было задано», и они означают разное.

Частые ошибки

  • Использовать null и undefined взаимозаменяемо — нарушает контракт со стороной, которая их читает.
  • Забывать, что typeof null возвращает 'object' (исторический баг языка).

Дополнительные вопросы

  • Что вернёт null == undefined и null === undefined?
  • Какое значение по умолчанию у параметра функции, если его не передать?

Какие значения в JavaScript считаются ложными (falsy)?

Junior

Ложных значений восемь: false, 0, -0, 0n, '' (пустая строка), null, undefined и NaN. Всё остальное — истинно, включая пустой массив и пустой объект.

Это важно при использовании if (value), оператора || для значений по умолчанию и логических преобразований.

Из практики: пустой массив [] и пустой объект {} истинны, и условие if (arr) не отличает пустой массив от заполненного — для проверки длины нужен arr.length.

Частые ошибки

  • Думать, что if (arr) проверит, что массив непустой.
  • Использовать value || defaultValue, когда 0 или '' — допустимое значение (тогда нужен ??).

Дополнительные вопросы

  • Чем оператор ?? отличается от ||?
  • Как преобразовать значение в boolean явно?

Что такое замыкание (closure) и приведите пример его использования.

Junior

Замыкание — это функция вместе с окружением, в котором она была создана. Она «помнит» переменные внешней области даже после того, как внешняя функция отработала.

Типичный пример — счётчик: функция createCounter возвращает функцию, которая инкрементирует приватную переменную count. Каждый вызов вернёт следующее число, и count остаётся недоступным извне.

Из практики: замыкания используют для приватных данных, мемоизации, debounce/throttle и привязки контекста в обработчиках событий.

Частые ошибки

  • Создавать замыкания в цикле с var — все они увидят одно и то же значение.
  • Хранить в замыкании большие данные — память не освободится, пока живёт ссылка на функцию.

Дополнительные вопросы

  • Что выведет for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0)?
  • Как с помощью замыкания реализовать функцию-однократку (once)?

Чем стрелочные функции отличаются от обычных?

Junior

Стрелочная функция не имеет собственного this, arguments, super и new.target. this берётся из окружения, где функция создана, а не вызвана.

Стрелочные функции нельзя использовать как конструкторы (new не сработает), у них нет prototype и они не могут быть генераторами.

Из практики: стрелки удобны для коротких колбэков (.map, обработчиков), особенно когда нужно сохранить this класса или внешнего объекта.

Частые ошибки

  • Использовать стрелочную функцию как метод объекта, ожидая, что this будет указывать на этот объект.
  • Передавать стрелочную функцию туда, где библиотека привязывает this через call/apply.

Дополнительные вопросы

  • Что выведет obj.method = () => this; obj.method() в strict-режиме?
  • Можно ли использовать arguments внутри стрелочной функции?

Что такое this в JavaScript и от чего зависит его значение?

Junior

this — это ссылка на объект, в контексте которого вызывается функция. Его значение определяется в момент вызова, а не объявления (за исключением стрелочных функций).

В обычной функции this зависит от способа вызова: obj.method() — this это obj; вызов через call/apply/bind — явно заданный; new Foo() — новый объект; просто foo() — undefined в strict mode или глобальный объект иначе.

Из практики: чтобы зафиксировать this, используют bind или стрелочные функции. Без понимания this легко получить ошибку 'cannot read property of undefined'.

Частые ошибки

  • Передавать obj.method как колбэк без bind — this теряется.
  • Думать, что в стрелочной функции this можно переопределить через call.

Дополнительные вопросы

  • Чем bind отличается от call и apply?
  • Что такое неявная и явная привязка this?

Какие способы создания объекта есть в JavaScript?

Junior

Самые частые: литерал { a: 1 }, конструктор new Object(), функция-конструктор с new, класс class, Object.create(proto), фабричная функция.

Разница в том, какой прототип получит объект и сколько boilerplate-кода. Литерал — самый частый, class и Object.create — когда важна явная цепочка прототипов.

Из практики: для большинства задач хватает литерала и class. Object.create нужен, когда хочется создать объект с конкретным прототипом или совсем без него (Object.create(null)).

Частые ошибки

  • Использовать new Object() вместо литерала без причины — это многословнее.
  • Забывать, что у литерала прототип — Object.prototype, и он содержит методы вроде toString.

Дополнительные вопросы

  • Зачем создавать объект без прототипа через Object.create(null)?
  • Чем фабричная функция лучше конструктора?

Что такое NaN и как проверить, что значение — NaN?

Junior

NaN — это значение типа number, означающее «не число». Появляется при невалидной математической операции: parseInt('abc'), 0/0, Math.sqrt(-1) и т.п.

Проверка через value === NaN не работает, потому что NaN не равен ничему, в том числе самому себе. Используйте Number.isNaN(value) или старую функцию isNaN (которая неявно приводит к числу).

Из практики: когда в UI появляется 'NaN', это почти всегда баг в парсинге пользовательского ввода — стоит проверить место, где строка превращается в число.

Частые ошибки

  • Использовать глобальный isNaN — он считает 'abc' NaN, потому что приводит к числу.
  • Не валидировать ввод перед арифметикой и получать NaN в БД или интерфейсе.

Дополнительные вопросы

  • Чем отличаются Number.isNaN и isNaN?
  • Как проверить, что строка парсится в число?

Что делает оператор расширения (spread) и оператор остатка (rest)?

Junior

Оператор ... один и тот же по виду, но работает по-разному в зависимости от контекста. В вызове функции или в литерале массива/объекта он разворачивает (spread) итерируемое: fn(...arr), [...arr1, ...arr2], { ...obj }.

В параметре функции или в деструктуризации он собирает (rest) оставшиеся аргументы или свойства: function fn(first, ...rest), const { a, ...others } = obj.

Из практики: spread удобен для копирования массивов и объектов на один уровень и для объединения. Rest — чтобы принять переменное число аргументов.

Частые ошибки

  • Считать { ...obj } глубокой копией — это поверхностное копирование.
  • Использовать rest не последним параметром — будет синтаксическая ошибка.

Дополнительные вопросы

  • Как глубоко скопировать объект?
  • Чем spread отличается от Object.assign?

Как работает деструктуризация массивов и объектов?

Junior

Деструктуризация — это синтаксис распаковки значений из массивов или свойств из объектов в отдельные переменные. const [a, b] = [1, 2] и const { name, age } = user.

Поддерживает значения по умолчанию (const { x = 10 } = obj), переименование (const { x: posX } = obj), вложенные структуры и rest (const [first, ...rest] = arr).

Из практики: помогает писать короче — особенно при работе с параметрами функций и ответами API. Хорошо сочетается с импортами модулей.

Частые ошибки

  • Забывать значение по умолчанию для отсутствующих полей.
  • Деструктурировать undefined — это TypeError. Защититесь через = {} в параметре функции.

Дополнительные вопросы

  • Как переименовать поле при деструктуризации?
  • Что произойдёт при const { a } = null?

Что такое шаблонные строки (template literals) и зачем они нужны?

Junior

Шаблонная строка — это строка в обратных кавычках, в которую можно вставлять выражения через ${...} и которая поддерживает переносы строк без \n.

В отличие от обычной строки с конкатенацией, шаблоны читабельнее: `Привет, ${name}, тебе ${age} лет` понятнее, чем 'Привет, ' + name + ', тебе ' + age + ' лет'.

Из практики: для многострочного HTML, SQL и сообщений об ошибках почти всегда удобнее шаблонные строки. Есть ещё tagged templates для пользовательской обработки.

Частые ошибки

  • Подставлять данные пользователя без экранирования — особенно в HTML.
  • Забывать, что ${expr} вычисляется сразу — для отложенного вычисления нужны функции.

Дополнительные вопросы

  • Что такое tagged template literals?
  • Как сделать многострочный SQL-запрос красиво?

Чем отличаются методы массива map, forEach, filter и reduce?

Junior

map — превращает каждый элемент по функции и возвращает новый массив той же длины. forEach — просто перебирает и ничего не возвращает, нужна ради побочных эффектов.

filter — возвращает новый массив с элементами, прошедшими условие. reduce — сворачивает массив к одному значению, накапливая результат через коллбэк (acc, item) => acc.

Из практики: forEach стоит избегать, когда можно выразить через map/filter/reduce — функциональный стиль читается лучше и легче тестируется.

Частые ошибки

  • Использовать map ради побочного эффекта — это сбивает с толку читателя.
  • Забывать вернуть значение в reducer — следующая итерация получит undefined.

Дополнительные вопросы

  • Как реализовать map через reduce?
  • Что вернёт [1,2,3].forEach(x => x * 2)?

Чем JSON.stringify отличается от JSON.parse и какие у них подводные камни?

Junior

JSON.stringify превращает значение JavaScript в JSON-строку. JSON.parse делает обратное — превращает строку в значение.

stringify теряет undefined в значениях (пропускает поля), функции, символы, Date превращает в строку, цикличные ссылки бросают TypeError. parse не понимает функции и комментарии.

Из практики: для глубокого клонирования простых данных подходит JSON.parse(JSON.stringify(obj)), но для Date/Map/Set/ref-циклов — нет, используйте structuredClone.

Частые ошибки

  • Сериализовать объект с Date и думать, что он останется Date.
  • Передавать в stringify объект с циклическими ссылками — будет ошибка.

Дополнительные вопросы

  • Что делает второй аргумент JSON.stringify (replacer)?
  • Как сериализовать BigInt?

Что такое цикл событий (event loop) простыми словами?

Junior

JavaScript однопоточный — за раз выполняется один кусок кода. Цикл событий — это механизм, который берёт задачи из очередей и поочерёдно подкидывает их движку, когда стек пуст.

Есть очередь макрозадач (setTimeout, события, ввод-вывод) и очередь микрозадач (промисы, queueMicrotask). После каждой макрозадачи целиком разгребается очередь микрозадач, потом берётся следующая макрозадача.

Из практики: понимание event loop помогает читать порядок логов из setTimeout/Promise и не блокировать главный поток тяжёлыми синхронными операциями.

Частые ошибки

  • Считать, что setTimeout(fn, 0) выполнится сразу — он ждёт текущей очереди и микрозадач.
  • Блокировать главный поток тяжёлой синхронной работой — UI зависнет.

Дополнительные вопросы

  • Что выведет Promise.resolve().then(() => console.log('p')) рядом с setTimeout(() => console.log('s'), 0)?
  • Чем отличается Node.js event loop от браузерного?

Что такое callback и какие у него проблемы?

Junior

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

Главные проблемы: callback hell (вложенность нескольких асинхронных операций), сложная обработка ошибок и потеря контекста выполнения. Это привело к появлению промисов.

Из практики: в современном коде асинхронные операции пишут через async/await поверх промисов. Callbacks остаются в обработчиках событий и в API некоторых старых библиотек.

Частые ошибки

  • Глубокая вложенность callbacks делает код нечитаемым и сложно тестируемым.
  • Забывать про error-first callback (Node.js): первый аргумент — ошибка.

Дополнительные вопросы

  • Чем промис лучше callback?
  • Что такое error-first callback в Node.js?

Что такое промис (Promise) и какие у него состояния?

Junior

Промис — это объект, представляющий результат асинхронной операции. У него три состояния: pending (в процессе), fulfilled (успешно завершён) и rejected (завершён с ошибкой).

После перехода из pending в fulfilled или rejected состояние больше не меняется — промис «settled». Цепочка обработки строится через .then, .catch, .finally.

Из практики: промис гарантирует, что обработчик вызовется не более одного раза, и упрощает компоновку асинхронных операций через цепочку.

Частые ошибки

  • Забывать обрабатывать ошибки — unhandled rejection ломает приложение.
  • Не возвращать промис из then — следующий обработчик не дождётся.

Дополнительные вопросы

  • Что делает Promise.all?
  • Чем .catch отличается от обработчика ошибок в .then?

Что делает async/await и как это связано с промисами?

Junior

async/await — это синтаксический сахар поверх промисов. Функция, помеченная async, всегда возвращает промис. Внутри неё await перед промисом приостанавливает выполнение до его разрешения.

Это позволяет писать асинхронный код в стиле обычного синхронного: const data = await fetchUser(); — без цепочек .then.

Из практики: async/await проще читать и отлаживать, особенно в сценариях с несколькими последовательными запросами. Ошибки ловятся обычным try/catch.

Частые ошибки

  • Использовать await в цикле, где можно было параллельно — это последовательное выполнение.
  • Забывать async на функции и удивляться, что await не работает.

Дополнительные вопросы

  • Как выполнить несколько асинхронных операций параллельно с async/await?
  • Можно ли использовать await в обычной функции?

Чем макрозадачи отличаются от микрозадач в JavaScript?

Middle

Макрозадачи — это setTimeout, setInterval, обработчики событий, операции ввода-вывода. Микрозадачи — это обработчики промисов, queueMicrotask и MutationObserver.

После каждой макрозадачи движок полностью разгребает очередь микрозадач, и только потом берёт следующую макрозадачу. Микрозадачи имеют приоритет.

Из практики: если в обработчике промиса добавлять новые промисы, они выполнятся до setTimeout(fn, 0). Это важно для понимания порядка событий и борьбы с гонками.

Частые ошибки

  • Создавать в микрозадаче бесконечную цепочку промисов — заблокирует рендеринг.
  • Считать queueMicrotask и setTimeout эквивалентными по приоритету.

Дополнительные вопросы

  • Что выведет в каком порядке: Promise.resolve().then(...), setTimeout(...), console.log?
  • Чем queueMicrotask отличается от Promise.resolve().then?

Чем Promise.all, Promise.allSettled, Promise.race и Promise.any отличаются?

Middle

Promise.all ждёт, пока все промисы успешно завершатся, и возвращает массив результатов. Если любой отвергнут — общий промис тоже отвергнут (fail-fast).

Promise.allSettled ждёт все промисы и возвращает массив объектов {status, value/reason} независимо от успеха. Promise.race возвращает результат первого завершённого (успех или ошибка). Promise.any возвращает первый успешный, ошибки — только если все упали.

Из практики: all — для строго связанных запросов; allSettled — когда нужно собрать все результаты, включая ошибки; race — для таймаутов; any — для отказоустойчивости.

Частые ошибки

  • Использовать Promise.all там, где одна ошибка не должна валить весь набор.
  • Забывать, что Promise.race завершается и при первой ошибке.

Дополнительные вопросы

  • Как сделать таймаут для fetch через Promise.race?
  • Что вернёт Promise.all с пустым массивом?

Что такое прототипное наследование в JavaScript?

Middle

У каждого объекта в JS есть скрытая ссылка на другой объект — прототип ([[Prototype]], доступный через __proto__ или Object.getPrototypeOf). При поиске свойства движок идёт по цепочке прототипов до Object.prototype или null.

Через прототип реализовано наследование: метод, объявленный на прототипе, доступен всем экземплярам, но хранится в одном месте.

Из практики: class — это синтаксический сахар над прототипным наследованием. Понимание цепочки помогает отлаживать неочевидные ошибки доступа к свойствам.

Частые ошибки

  • Менять Object.prototype — это сломает все объекты в проекте.
  • Путать prototype (свойство функции-конструктора) и __proto__ (ссылка экземпляра).

Дополнительные вопросы

  • Чем Object.create отличается от new?
  • Как реализовать множественное наследование?

Чем class в JavaScript отличается от функции-конструктора?

Middle

Технически class — это синтаксический сахар над функцией-конструктором и прототипом. Под капотом всё то же самое: методы кладутся в prototype, поля экземпляра — в this.

Отличия в поведении: class нельзя вызвать без new, методы класса не перечисляются (non-enumerable), они исполняются в strict mode, есть приватные поля #x, статические члены и нативное наследование через extends/super.

Из практики: для нового кода используйте class — он понятнее и предсказуемее. Прямые манипуляции с prototype оставьте для библиотек и редких случаев.

Частые ошибки

  • Вызывать class без new — будет TypeError.
  • Забывать вызвать super() в наследнике — будет ReferenceError при обращении к this.

Дополнительные вопросы

  • Что такое статические методы и поля?
  • Как сделать приватное поле в class?

Что делают bind, call и apply?

Middle

Все три задают, каким будет this при вызове функции. call и apply вызывают функцию сразу: call принимает аргументы списком, apply — массивом. bind возвращает новую функцию с зафиксированным this и опциональными аргументами.

fn.call(obj, 1, 2); fn.apply(obj, [1, 2]); const bound = fn.bind(obj, 1); bound(2);

Из практики: bind часто используют для привязки обработчиков событий, чтобы внутри был нужный this. call/apply — для одноразового вызова с подменой контекста.

Частые ошибки

  • Пытаться bind стрелочную функцию — this не поменяется.
  • Несколько раз bind подряд — только первый имеет эффект.

Дополнительные вопросы

  • Можно ли расslucать привязку через bind?
  • Чем bind отличается от обёртки в стрелочную функцию?

Чем модули ESM отличаются от CommonJS?

Middle

CommonJS (require/module.exports) — синхронный, динамический, используется в Node.js по умолчанию. Импорты выполняются в момент вызова require.

ESM (import/export) — статический, асинхронный, нативный стандарт для браузеров и современного Node.js. Импорты разрешаются на этапе парсинга, поддерживает динамический import().

Из практики: статичность ESM позволяет инструментам делать tree-shaking — выкидывать неиспользуемый код. В Node.js файлы с расширением .mjs или с "type": "module" в package.json — это ESM.

Частые ошибки

  • Смешивать require и import в одном файле без понимания interop.
  • Использовать __dirname/__filename в ESM — их там нет, нужны import.meta.url.

Дополнительные вопросы

  • Что такое tree-shaking и почему он не работает с CommonJS?
  • Как сделать динамический импорт?

Чем Map отличается от обычного объекта?

Middle

Map — это коллекция пар ключ-значение, где ключом может быть что угодно (включая объект и функцию), а порядок вставки гарантирован.

У обычного объекта ключи — только строки и символы, есть наследуемые свойства из Object.prototype, и порядок ключей не всегда строго гарантирован для числовых строк.

Из практики: Map выбирают, когда ключи динамические, когда нужны произвольные типы ключей, или когда важна производительность вставки/удаления.

Частые ошибки

  • Использовать объект как мап и натыкаться на коллизию с прототипными свойствами (toString и т.п.).
  • Сериализовать Map через JSON.stringify — он отдаст пустой объект.

Дополнительные вопросы

  • Когда быстрее: Map или объект?
  • Что такое WeakMap и зачем он?

Что такое WeakMap и WeakSet, и в чём их особенность?

Middle

WeakMap и WeakSet — это коллекции, где ключами (Map) или значениями (Set) могут быть только объекты, и эти ссылки слабые. Сборщик мусора может удалить такой объект, если на него нет других ссылок.

У них нет методов перебора (no keys/values/entries) и нет свойства size — иначе можно было бы наблюдать, когда GC удалит объект.

Из практики: подходят для хранения «связанных» данных с объектом, которые должны умереть вместе с ним — кеши, приватные поля, метаинформация о DOM-узлах.

Частые ошибки

  • Пытаться использовать примитивный ключ — будет TypeError.
  • Полагаться на момент очистки — GC недетерминирован.

Дополнительные вопросы

  • В чём практическая разница между Map и WeakMap?
  • Как реализовать приватные поля через WeakMap?

Что такое debounce и throttle, и когда что применять?

Middle

Debounce откладывает выполнение функции, пока не пройдёт N миллисекунд без новых вызовов. Часто используют для поиска по мере ввода — отправляем запрос только когда пользователь закончил печатать.

Throttle ограничивает частоту вызовов: функция выполнится не чаще, чем раз в N мс, даже если её зовут чаще. Подходит для обработчиков скролла, resize, мыши.

Из практики: при поиске и валидации формы — debounce. При прогресс-баре и подгрузке при скролле — throttle.

Частые ошибки

  • Использовать debounce для скролла — поведение будет дёрганым.
  • Не очищать таймер при размонтировании компонента — утечка.

Дополнительные вопросы

  • Как реализовать debounce в 5 строк?
  • Что вернёт первый вызов throttle?

Что такое AbortController и для чего нужен?

Middle

AbortController — это встроенный механизм, позволяющий отменить выполнение асинхронной операции. Создаёте controller, передаёте controller.signal в fetch (или другую совместимую операцию), а вызовом controller.abort() отменяете её.

Отменённый fetch отвергается с ошибкой AbortError, которую можно поймать в catch и не обрабатывать как настоящую ошибку.

Из практики: критично для борьбы с гонками — когда пользователь быстро меняет параметры поиска, отменяете старый запрос перед отправкой нового.

Частые ошибки

  • Не различать AbortError и реальную ошибку сети — пользователь увидит ложное сообщение.
  • Переиспользовать один controller для нескольких независимых запросов.

Дополнительные вопросы

  • Как сделать таймаут запроса через AbortController?
  • Поддерживают ли AbortController другие API кроме fetch?

Чем поверхностное копирование отличается от глубокого, и как их сделать?

Middle

Поверхностное копирование копирует только верхний уровень. Вложенные объекты остаются общими по ссылке. Делается через { ...obj }, Object.assign({}, obj), [...arr], arr.slice().

Глубокое копирование клонирует всё, включая вложенные структуры. Делается через structuredClone(obj) (современный способ), JSON.parse(JSON.stringify(obj)) (с ограничениями) или библиотеку lodash.cloneDeep.

Из практики: JSON-трюк теряет Date, undefined, функции, Map/Set и не работает с циклическими ссылками. structuredClone справляется со всем этим.

Частые ошибки

  • Сделать { ...obj } и удивиться, что изменение вложенного поля видно в копии.
  • Применять JSON.parse(JSON.stringify(...)) к объекту с Date — получите строки.

Дополнительные вопросы

  • Когда выбрать structuredClone, а когда — lodash.cloneDeep?
  • Как глубоко слить два объекта?

Что такое currying и для чего применяется?

Middle

Currying — это преобразование функции, принимающей несколько аргументов, в цепочку функций, каждая из которых принимает один аргумент. add(a, b, c) превращается в add(a)(b)(c).

Это позволяет частично применять функции: const add5 = curriedAdd(5) даст функцию, ждущую остальные аргументы.

Из практики: помогает строить переиспользуемые специализированные функции из универсальных. В библиотеках типа Ramda на этом построена композиция.

Частые ошибки

  • Перебарщивать с каррированием — для двух аргументов часто проще обычная функция или bind.
  • Путать каррирование с частичным применением — это разные понятия.

Дополнительные вопросы

  • Чем currying отличается от partial application?
  • Как реализовать универсальную функцию curry в 10 строк?

Какие типовые причины утечек памяти в JavaScript?

Middle

Самые частые: глобальные переменные, не снятые обработчики событий, незавершённые таймеры (setInterval), замыкания, удерживающие большие данные, и кеши без ограничения размера.

В браузере — отсоединённые DOM-узлы, на которые сохранились ссылки в JS, тоже копят память. В Node.js — пулы соединений, не закрытые потоки, listeners на EventEmitter.

Из практики: для диагностики снимают heap snapshot в DevTools и ищут «отсоединённые» объекты. Регулярная проверка снапшотов до и после действия — лучший способ найти течь.

Частые ошибки

  • Не снимать addEventListener при размонтировании компонента.
  • Хранить ссылки на DOM-узлы в массивах/Map после удаления элемента.

Дополнительные вопросы

  • Чем поможет WeakMap?
  • Как поймать утечку через Chrome DevTools?

Чем оператор ?? отличается от ||?

Middle

Оператор || возвращает правый операнд, если левый — любое ложное значение (false, 0, '', null, undefined, NaN). Оператор ?? возвращает правый только если левый — именно null или undefined.

Это критично, когда 0 или пустая строка — допустимое значение: const port = config.port ?? 3000 оставит port=0, а || заменит на 3000.

Из практики: для значений по умолчанию в современных приложениях чаще нужно ??. Используйте || только если действительно хотите заменять все falsy.

Частые ошибки

  • Применять || к настройкам, где 0 или '' валидны — затрёт пользовательский ввод.
  • Смешивать ?? и || в одном выражении без скобок — синтаксическая ошибка.

Дополнительные вопросы

  • Что такое оператор опциональной цепочки ?.?
  • Как сделать значение по умолчанию для свойства объекта при деструктуризации?

Что такое опциональная цепочка (?.) и когда её использовать?

Middle

Опциональная цепочка позволяет безопасно обращаться к свойствам и методам цепочкой. user?.profile?.address?.city — если в любом звене null или undefined, всё выражение вернёт undefined без TypeError.

Также работает для вызовов методов obj.method?.() и индексации arr?.[0].

Из практики: убирает шум из проверок if (a && a.b && a.b.c). Особенно полезно при работе с ответами API, где структура может прийти неполной.

Частые ошибки

  • Глушить настоящие ошибки — если по логике поле обязательно, лучше явно бросить.
  • Использовать ?. там, где можно гарантировать структуру — теряется типобезопасность.

Дополнительные вопросы

  • Что вернёт null?.method()?
  • Как сочетать ?. с ?? для безопасного значения по умолчанию?

Что такое Iterator и Iterable в JavaScript?

Middle

Iterable — это объект, у которого есть метод [Symbol.iterator](), возвращающий Iterator. Iterator — это объект с методом next(), возвращающим { value, done }.

На итерируемых работают for...of, спред (...arr), деструктуризация и Array.from. Встроенные итерируемые: Array, String, Map, Set, NodeList, аргументы функции.

Из практики: реализовав протокол Iterable, свой класс получает поддержку всех языковых конструкций. Это удобно для коллекций, потоков данных, бесконечных последовательностей.

Частые ошибки

  • Перепутать Iterable и Iterator — у Iterator нет Symbol.iterator (хотя многие итераторы тоже Iterable).
  • Создавать бесконечный итератор без условия выхода в for...of.

Дополнительные вопросы

  • Чем for...of отличается от for...in?
  • Как сделать свой Iterable в одну строку через generator?

Что такое Generator-функция и для чего она нужна?

Middle

Generator-функция объявляется через function* и возвращает объект, реализующий протокол Iterator. Внутри используется yield, который приостанавливает выполнение и возвращает значение.

При каждом вызове .next() выполнение продолжается до следующего yield. Удобно для ленивых последовательностей, кастомных итераторов, реализации корутин до появления async/await.

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

Частые ошибки

  • Думать, что generator-функция исполняется при объявлении — нет, нужно вызвать и потом .next().
  • Передавать значение через .next(value) и не понимать, куда оно попадает.

Дополнительные вопросы

  • Как остановить генератор?
  • Чем yield отличается от return внутри генератора?

Что такое Proxy и для каких задач его используют?

Middle

Proxy — это обёртка вокруг объекта, перехватывающая операции с ним: чтение и запись свойств, вызов, перебор ключей, has, delete и другие. Перехватчики называют trap.

Создаётся: new Proxy(target, handler). Handler — объект с trap-ами вроде get(target, prop) и set(target, prop, value).

Из практики: Vue 3 на Proxy построил реактивность. Также применяют для логирования доступа, валидации, ленивого вычисления, виртуальных свойств.

Частые ошибки

  • Использовать Proxy там, где можно просто getter/setter — оверкилл.
  • Считать, что Proxy перехватит всё — некоторые операции (typeof, ===) не перехватываются.

Дополнительные вопросы

  • Что такое Reflect и как он связан с Proxy?
  • Какие операции не перехватываются Proxy?

Что такое Reflect API?

Middle

Reflect — это встроенный объект с методами, дублирующими операции с объектами в функциональной форме. Reflect.get, Reflect.set, Reflect.has, Reflect.construct, Reflect.apply и т.д.

Эти методы возвращают результат операции напрямую, без бросков, и удобны в Proxy: trap обычно делегирует Reflect.get(target, prop, receiver) с теми же аргументами.

Из практики: пара Proxy + Reflect — стандартный приём для тонкой настройки доступа к объекту без перепутывания семантики.

Частые ошибки

  • Использовать Reflect, когда обычный синтаксис обходится без него — лишний шум.
  • Путать Reflect.construct и new — поведение почти такое же, но не идентичное.

Дополнительные вопросы

  • Чем Reflect.get(obj, prop) отличается от obj[prop]?
  • В каких ситуациях лучше Reflect.construct?

Что такое Symbol и для чего он нужен?

Middle

Symbol — это примитивный тип. Каждый созданный через Symbol() уникален и не равен никакому другому, даже с тем же description.

Используют как ключи свойств, чтобы избежать коллизий, и для well-known symbols (Symbol.iterator, Symbol.asyncIterator, Symbol.toPrimitive), которыми расширяют поведение языка для своих объектов.

Из практики: для приватных полей библиотек, чтобы конечный пользователь случайно не попал в них. И для интеграции с языковыми протоколами (для своего объекта реализовать [Symbol.iterator]).

Частые ошибки

  • Сериализовать в JSON объект с symbol-ключами — они теряются.
  • Путать Symbol() и Symbol.for() — последний делает интернированный символ.

Дополнительные вопросы

  • Чем Symbol() отличается от Symbol.for()?
  • Что такое Symbol.iterator?

Что такое Tail Call Optimization и есть ли он в JavaScript?

Middle

TCO — это оптимизация, при которой возврат значения через вызов функции (return f(...)) не создаёт новый stack frame, а переиспользует текущий. Это позволяет писать глубокую рекурсию без переполнения стека.

В стандарте ECMAScript TCO есть для strict mode, но движки V8 и большинство других её не реализуют. Только Safari/JavaScriptCore поддерживает.

Из практики: на TCO в JS рассчитывать нельзя. Для глубокой рекурсии в Node.js обычно переписывают на цикл или trampolining.

Частые ошибки

  • Писать алгоритм в расчёте на TCO в V8 — упадёт RangeError.
  • Использовать рекурсию там, где цикл явно проще.

Дополнительные вопросы

  • Что такое trampoline в функциональном программировании?
  • Какой максимальный размер стека в Node.js?

Что такое Web Worker и для каких задач его используют?

Middle

Web Worker — это механизм в браузере запустить JS в отдельном потоке. Главный поток отвечает за UI, worker — за тяжёлую работу. Обмен через postMessage и событие message.

Worker не имеет доступа к DOM и window. Можно использовать fetch, IndexedDB, библиотеки, не зависящие от DOM. Бывают dedicated, shared и service workers (последние — отдельная история про оффлайн).

Из практики: для тяжёлых вычислений (парсинг большого JSON, обработка изображений, криптография) Web Worker помогает не блокировать интерфейс. Минус — overhead на сериализацию через postMessage.

Частые ошибки

  • Передавать огромные объекты через postMessage и удивляться задержкам.
  • Думать, что Web Worker имеет доступ к DOM.

Дополнительные вопросы

  • Что такое Transferable Objects?
  • Чем Service Worker отличается от Web Worker?

Что такое Service Worker и для каких задач?

Middle

Service Worker — это скрипт в браузере, который работает в отдельном потоке, проксирует сетевые запросы страницы и переживает закрытие вкладки. Регистрируется один раз на origin, работает с HTTPS.

Применяют для: оффлайн-режима (кеширование ассетов и API), push-уведомлений, фонового sync, ускорения отображения (stale-while-revalidate).

Из практики: основа PWA. Реализуют через cache API: на install кладут shell, на fetch перехватывают и решают, идти ли в сеть или брать из кеша.

Частые ошибки

  • Забыть про lifecycle (install/activate/clients.claim) и получить старую версию у пользователей.
  • Кешировать всё подряд и не делать стратегии инвалидации.

Дополнительные вопросы

  • Что такое cache-first и network-first стратегии?
  • Как обновить service worker без рестарта пользователем?

Что такое IIFE и зачем он использовался?

Middle

IIFE (Immediately Invoked Function Expression) — функция, которая объявляется и тут же вызывается: (function() { ... })(). До ES6 это был основной способ создать изолированную область видимости.

С появлением let, const и модулей ESM нужда в IIFE почти исчезла — let даёт блочную область, а модули по умолчанию изолированы.

Из практики: в современном коде IIFE остался как редкий приём — для одноразовой инициализации, в bookmarklet-ах, в minified-коде библиотек.

Частые ошибки

  • Писать IIFE в новом коде — выглядит архаично, есть лучшие способы.
  • Забывать про возврат значения из IIFE, ожидая, что переменная получит результат.

Дополнительные вопросы

  • Чем IIFE отличается от обычной функции?
  • Зачем IIFE в стиле UMD-обёртки?

Что такое prototype pollution и как защититься?

Middle

Prototype pollution — это уязвимость, при которой злоумышленник через подконтрольный ввод модифицирует Object.prototype или другой общий прототип. Это влияет на все объекты в приложении.

Часто происходит в небезопасных merge-функциях, которые рекурсивно копируют свойства, не проверяя ключ __proto__ или constructor.prototype.

Из практики: защита — использовать Object.create(null) для словарей, проверять ключи перед merge, использовать Map вместо объекта-словаря, обновлять lodash и аналоги (исторически были CVE).

Частые ошибки

  • Считать, что Object.assign защищает — он не глубокий, но сам по себе тоже может загрязнять при правильной нагрузке.
  • Не валидировать ключи в API, которое принимает произвольный JSON.

Дополнительные вопросы

  • Какие версии lodash были уязвимы?
  • Как сделать безопасный deep merge?

Что такое closure compiler / tree-shaking / minification?

Middle

Minification — сокращение кода: убираются пробелы, переименовываются переменные в a, b, c. Уменьшает размер бандла.

Tree-shaking — удаление неиспользуемого кода из бандла на основе статического анализа ESM-импортов. Если import { a } from 'lib' использует только a, остальное может быть вырезано.

Closure Compiler от Google — продвинутый компилятор, делающий и minification, и более глубокие оптимизации (inlining, dead code elimination). Из практики: для большинства проектов хватает esbuild/swc/terser. Closure Compiler ставят там, где важен каждый килобайт.

Частые ошибки

  • Tree-shaking не работает на CommonJS-импортах.
  • Сторонние библиотеки с побочными эффектами при импорте мешают tree-shaking.

Дополнительные вопросы

  • Что такое sideEffects: false в package.json?
  • Чем esbuild быстрее webpack?

Чем var hoisting отличается от function hoisting?

Middle

Объявление var поднимается, и переменная инициализируется как undefined. Но присваивание остаётся на месте — то есть до строки присваивания значение всегда undefined.

Function declaration (function foo() {}) поднимается целиком вместе с телом — её можно вызвать до строки объявления.

Из практики: function expression (const foo = function() {}) поднимается как const-переменная — её нельзя использовать до строки объявления (TDZ). Этим объясняется, почему один и тот же 'foo' работает или нет в зависимости от формы объявления.

Частые ошибки

  • Считать, что function expression тоже поднимается с телом.
  • Полагаться на function declaration внутри блока if — поведение разное в разных движках до строгой стандартизации.

Дополнительные вопросы

  • Что такое TDZ?
  • Поднимается ли class?

Что такое модули с динамическим импортом и зачем они?

Middle

import('./module.js') — это функция, возвращающая Promise с экспортами модуля. В отличие от статического import, модуль грузится в момент вызова, а не на старте.

Используют для code splitting (загружать страницу/модуль по требованию), для условной загрузки (фича только при флаге), для отложенной инициализации тяжёлых зависимостей.

Из практики: SPA-фреймворки активно применяют динамический import для разделения по маршрутам — первоначальная загрузка содержит только нужное.

Частые ошибки

  • Делать import() в горячем пути — каждый вызов после первого попадает в кеш, но первый тормозит.
  • Не обрабатывать ошибку загрузки — пользователь увидит белый экран.

Дополнительные вопросы

  • Как обработать ошибку chunk-load?
  • Что такое /* webpackChunkName: 'foo' */?

Что такое event delegation в JavaScript?

Middle

Event delegation — это паттерн, когда обработчик события навешивают на общего предка, а не на каждый дочерний элемент. Используется bubbling — событие всплывает по дереву DOM до предка.

Внутри обработчика по event.target определяют, какой именно элемент кликнули, и реагируют. Это масштабируется на динамически добавляемые элементы без перенавешивания обработчиков.

Из практики: для списков с динамическим содержимым (комментарии, todo) делегирование сильно упрощает код и снижает память.

Частые ошибки

  • Использовать делегирование для событий, не всплывающих (focus, blur, mouseenter) — нужны capture или специальные варианты.
  • Не учитывать, что event.target может быть вложенным узлом.

Дополнительные вопросы

  • Чем event.target отличается от event.currentTarget?
  • Какие события не всплывают?

Что такое CSP и как он связан с JavaScript-безопасностью?

Middle

Content Security Policy — это HTTP-заголовок, которым сервер сообщает браузеру, откуда можно грузить скрипты, стили, изображения и какие inline-скрипты разрешены. Снижает риск XSS.

Типичная политика: script-src 'self' https://trusted.cdn запрещает inline-скрипты и скрипты с других доменов. nonce или hash позволяют точечно разрешить нужные inline-скрипты.

Из практики: внедрение CSP без поломки сайта требует аудита — какие скрипты сейчас грузятся. Часто начинают с report-only режима, чтобы видеть нарушения без блокировки.

Частые ошибки

  • Использовать unsafe-inline и тем самым обнулять защиту.
  • Включать строгий CSP сразу на проде — есть риск сломать аналитику/рекламу.

Дополнительные вопросы

  • Что такое CSP nonce?
  • Чем Trusted Types помогают в борьбе с XSS?

Что такое async iterator и for await...of?

Middle

Async iterator — это итератор, у которого next() возвращает Promise<{ value, done }>. Объявляется через Symbol.asyncIterator. Перебирается через for await...of.

Используется для потоков, которые приходят асинхронно: чтение строк файла, события из WebSocket, постраничный API.

Из практики: for await...of даёт сильно более чистый код для потоковой обработки. В Node.js потоки (Readable) поддерживают async iteration: for await (const chunk of stream) {...}.

Частые ошибки

  • Использовать for await...of в обычной функции — нужен async.
  • Не обрабатывать ошибку в async iterator — пропадёт без catch.

Дополнительные вопросы

  • Как сделать свой async iterator?
  • Чем async generator отличается от обычного?

Что такое typed arrays и для чего они нужны?

Middle

Typed arrays (Uint8Array, Int16Array, Float32Array и т.д.) — это массивы фиксированного размера и типа над общим буфером ArrayBuffer. В отличие от обычного Array, элементы хранятся компактно, без боксинга.

Применяют там, где важна производительность и низкий уровень: работа с бинарными данными, WebSockets с бинарным фреймом, аудио/видео обработка, WebGL.

Из практики: для обычной бизнес-логики не нужны. Но для буферов изображений, сетевых протоколов, числовых вычислений — типовой выбор.

Частые ошибки

  • Считать, что Uint8Array — обычный массив с set/push — методов меньше.
  • Не учитывать endianness при работе с DataView.

Дополнительные вопросы

  • Чем ArrayBuffer отличается от типизированного массива?
  • Что такое DataView?

Что такое generator delegation (yield*)?

Middle

yield* делегирует управление другому итерируемому или генератору. Внутри generatorA: yield* generatorB() заставляет generatorA по очереди отдавать все значения generatorB перед тем, как продолжить свой код.

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

Из практики: для обхода дерева удобно — каждый уровень рекурсивно делегирует обход. Это аккуратнее, чем ручная склейка через for-of+yield.

Частые ошибки

  • Думать, что yield* нужен только для генераторов — он работает с любым Iterable.
  • Получать через .next(value) и не понимать, что значение прокидывается во вложенный генератор.

Дополнительные вопросы

  • Чем yield* отличается от yield в for-of?
  • Что вернётся, если делегировать обычному массиву?

Что такое FinalizationRegistry и WeakRef?

Middle

WeakRef хранит слабую ссылку на объект — она не мешает сборщику мусора освободить объект, если других ссылок нет. Значение получают через ref.deref(), который может вернуть undefined.

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

Ключевая оговорка: вызов колбэка НЕ гарантирован — может не сработать вовсе или с большой задержкой, порядок и тайминг непредсказуемы. Нельзя полагаться на finalizer для критичной логики.

Из практики: API молоды и редко нужны — кеши, освобождающиеся по мере необходимости, и интеграции с WASM/native, где есть внешние ресурсы.

Частые ошибки

  • Полагаться, что FinalizationRegistry вызовется быстро — GC недетерминирован.
  • Использовать WeakRef там, где хватит WeakMap.

Дополнительные вопросы

  • Чем WeakMap отличается от WeakRef?
  • Когда FinalizationRegistry оправдан?

Что такое top-level await в модулях?

Middle

Top-level await — это возможность использовать await на верхнем уровне модуля ESM, без обёртки в async-функцию. Например, const data = await fetch(...).then(r => r.json()).

Модуль с top-level await асинхронно резолвится — все, кто его импортирует, ждут завершения. Поддерживается в современных браузерах и Node.js (с .mjs или type: module).

Из практики: удобно для динамической конфигурации, для загрузки данных перед инициализацией модуля. Минус — задерживает старт всего графа модулей.

Частые ошибки

  • Использовать top-level await в горячем пути загрузки страницы — все импорты ждут.
  • Не учитывать, что в CommonJS такого нет.

Дополнительные вопросы

  • Как top-level await влияет на параллелизм импорта?
  • Можно ли поймать ошибку top-level await?

Что выведет console.log(typeof null) и почему?

Junior

typeof null возвращает 'object'. Это исторический баг языка из первых версий, оставленный для совместимости.

Реально null — это примитив, отдельный тип. Проверять на null корректно через value === null.

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

Частые ошибки

  • Полагаться на typeof для отличия объекта от null.
  • Считать, что typeof — это совсем точный тип. typeof function — 'function', что тоже частный случай.

Дополнительные вопросы

  • Как корректно проверить, что значение — объект?
  • Что вернёт typeof для массива?

Чем var hoisting опасен в циклах?

Junior

var имеет функциональную область, значит в цикле for (var i=0; i<3; i++) переменная i одна на все итерации. Замыкания внутри цикла «захватят» её, и все увидят финальное значение.

Решение: использовать let — у него блочная область, каждой итерации своя переменная.

Из практики: классический баг — for (var i=0; i<3; i++) setTimeout(()=>console.log(i),0). Выведет 3 3 3. С let — 0 1 2.

Частые ошибки

  • Использовать var в современном коде.
  • Думать, что замыкание захватывает значение, а не ссылку.

Дополнительные вопросы

  • Чем let решает эту проблему?
  • Как было до ES6: IIFE с параметром.

Что такое event bubbling и capturing?

Middle

Когда событие происходит на DOM-узле, оно идёт по дереву в три фазы: capturing (от document вниз к target), target (на самом элементе), bubbling (от target обратно вверх).

Большинство обработчиков навешиваются на bubbling. Третий аргумент addEventListener(true) — capturing. event.stopPropagation() прерывает дальнейшее распространение.

Из практики: для event delegation важен bubbling — обработчик на предке ловит события потомков.

Частые ошибки

  • Использовать stopPropagation там, где это не нужно — другие листенеры перестанут работать.
  • Не знать, что некоторые события не всплывают (focus, blur).

Дополнительные вопросы

  • Какие события не всплывают?
  • Что такое event.stopImmediatePropagation?

Что такое preventDefault и stopPropagation?

Junior

preventDefault отменяет действие по умолчанию (переход по ссылке, отправка формы, прокрутка). stopPropagation останавливает дальнейшее распространение события (bubbling/capturing).

Это разные вещи: preventDefault не останавливает распространение, stopPropagation не отменяет default action.

Из практики: оба часто нужны вместе при кастомной обработке — например, обработка клика по ссылке без перехода.

Частые ошибки

  • Путать preventDefault и stopPropagation.
  • preventDefault на passive listener — не сработает (для скролла).

Дополнительные вопросы

  • Что такое passive event listener?
  • Как отменить событие на нескольких уровнях?

Что такое immutability и зачем она в JS?

Middle

Иммутабельность — это подход, когда вместо изменения объекта создаётся новая копия с изменениями. {...obj, name: 'X'} вместо obj.name = 'X'.

Это даёт предсказуемость (никто не изменит ваши данные тайно), упрощает сравнение (если ссылка изменилась — значит, изменилось содержимое) и работает дружелюбно с React/Redux.

Из практики: для UI-фреймворков почти обязательно. Object.freeze() даёт runtime-защиту, но это редкий случай.

Частые ошибки

  • Считать spread глубокой копией — он поверхностный.
  • Гнаться за immutable там, где она не нужна — лишние аллокации.

Дополнительные вопросы

  • Что такое Immer и зачем он?
  • Чем shallow copy отличается от deep?

Что такое pure function и почему её предпочитают?

Middle

Чистая функция: при одинаковых аргументах всегда возвращает одинаковый результат и не имеет побочных эффектов.

Их легко тестировать, мемоизировать, рассуждать о них. Композиция чистых функций предсказуема.

Из практики: бизнес-логику стараются писать чистыми функциями. Грязные части (IO, время, случайные числа) изолируют в отдельных слоях.

Частые ошибки

  • Считать функцию чистой, если она зависит от глобальной переменной — это побочный эффект.
  • Делать всё абсолютно чистым — некоторые операции (логирование, fetch) — это и есть IO.

Дополнительные вопросы

  • Что такое side effect?
  • Как тестировать функцию, зависящую от Date.now?

Что такое composition vs inheritance?

Middle

Inheritance — расширение класса через наследование. Composition — собирание объекта из других объектов/функций.

Композиция гибче — можно комбинировать поведение из разных мест. В JS это часто выражается через миксины, hooks, передачу функций.

Из практики: правило «prefer composition over inheritance» особенно сильно в JS. Глубокие иерархии классов в JS обычно — признак плохого дизайна.

Частые ошибки

  • Строить деревья классов на 5 уровней — рефакторить ад.
  • Считать композицию и наследование взаимоисключающими — часто их комбинируют.

Дополнительные вопросы

  • Что такое mixin?
  • Как в React hooks реализуют композицию?

Что такое hoisting class в JavaScript?

Middle

class объявления поднимаются, но остаются в temporal dead zone до строки объявления — нельзя использовать до неё. В отличие от function declaration, который полностью поднимается.

Это сделано намеренно для предсказуемости.

Из практики: помните, что class — это не сахар над function declaration в плане hoisting. ReferenceError при попытке создать экземпляр до объявления — норма.

Частые ошибки

  • Полагать, что class можно использовать до объявления, как function.
  • Не понимать, почему ReferenceError.

Дополнительные вопросы

  • Чем class declaration отличается от class expression?
  • Что такое TDZ для class?

Чем for...in отличается от for...of?

Junior

for...in перебирает имена перечислимых свойств (включая наследуемые) — обычно по ключам объекта. for...of перебирает значения итерируемого (Array, String, Map, Set).

На массиве for...in даёт строки '0', '1', '2'... — но опасно, потому что захватит наследуемые свойства. for...of даёт сами значения.

Из практики: для массивов всегда for...of или forEach/map. for...in — только для перебора ключей объекта (часто проще Object.keys + forEach).

Частые ошибки

  • Использовать for...in на массиве — медленно и захватывает лишнее.
  • Полагать, что for...in возвращает значения.

Дополнительные вопросы

  • Что такое for await...of?
  • Чем Object.entries удобнее?

Что такое prototype chain?

Middle

У каждого объекта есть скрытая ссылка [[Prototype]] на другой объект. Поиск свойства идёт вверх по цепочке: сам объект → его прототип → прототип прототипа → ... → Object.prototype → null.

Это и есть основа наследования в JS. Class — синтаксический сахар над этим механизмом.

Из практики: помнить, что property lookup может пройти длинную цепочку. Бенчмарки показывают, что это редко проблема, но deep nesting усложняет отладку.

Частые ошибки

  • Менять Object.prototype — глобальные последствия.
  • Создавать длинные цепочки прототипов в hot path.

Дополнительные вопросы

  • Чем __proto__ отличается от prototype?
  • Что такое Object.create(null)?

Что такое event capturing vs target vs bubbling?

Middle

Три фазы события: capturing (от window до target), target (на самом элементе), bubbling (от target обратно вверх).

addEventListener третий аргумент true — capturing, false (по умолчанию) — bubbling.

Из практики: большинство обработчиков на bubbling. capturing нужен в редких случаях, например, для перехвата clicks на overlay.

Частые ошибки

  • Использовать capturing без необходимости — путаница.
  • Не понимать, что некоторые события не всплывают.

Дополнительные вопросы

  • Какие события не всплывают?
  • Что такое event.stopPropagation в фазе capturing?

Что такое queueMicrotask и почему он нужен?

Middle

queueMicrotask(fn) добавляет функцию в очередь микрозадач. Выполнится после текущего кода, но до следующего макрозадачи (setTimeout, setInterval).

Похоже на Promise.resolve().then(fn), но без создания промиса. Полезно для библиотек.

Из практики: для большинства приложений Promise.then делает то же самое. queueMicrotask — оптимизация, когда не нужен Promise.

Частые ошибки

  • Создавать бесконечный цикл микрозадач — заблокирует рендеринг.
  • Полагать, что setTimeout(fn, 0) и queueMicrotask эквивалентны.

Дополнительные вопросы

  • Чем queueMicrotask отличается от setTimeout?
  • Когда выбрать каждый?

Что такое Object.freeze vs Object.seal?

Middle

Object.freeze — полная заморозка: нельзя добавить, удалить, изменить свойства. Object.seal — нельзя добавить или удалить, но можно изменить существующие.

Обе функции поверхностные — вложенные объекты не замораживаются.

Из практики: freeze полезен для констант, конфигов. Для глубокой защиты нужен рекурсивный freeze. В TypeScript часто заменяют readonly.

Частые ошибки

  • Считать freeze глубокой защитой — нет.
  • Использовать seal там, где можно freeze — путаница.

Дополнительные вопросы

  • Что такое Object.preventExtensions?
  • Чем как const отличается от Object.freeze?

Что такое error в Promise и как ловить unhandled?

Middle

Если в промисе бросается исключение и нет .catch, возникает unhandled promise rejection. В браузере событие 'unhandledrejection' на window, в Node.js — 'unhandledRejection' на process.

С Node.js 15+ unhandled rejection крашит процесс. До этого был warning.

Из практики: всегда добавляйте обработку ошибок. Глобальный listener — последняя линия обороны (логирование, отправка в Sentry).

Частые ошибки

  • Не обрабатывать ошибки в Promise.all — одна ошибка отвергает весь набор.
  • Полагать, что .then без onError проигнорирует ошибку.

Дополнительные вопросы

  • Чем Promise.allSettled помогает?
  • Как настроить Sentry для unhandled rejections?

Что такое event loop в Node.js?

Middle

Node.js event loop похож на браузерный, но имеет специфичные фазы: timers, pending callbacks, idle, poll, check (setImmediate), close callbacks.

Микрозадачи (process.nextTick, Promise) выполняются между фазами.

Из практики: понимание фаз помогает отлаживать порядок setTimeout vs setImmediate. nextTick имеет приоритет над промисами.

Частые ошибки

  • Использовать process.nextTick рекурсивно — заблокирует event loop.
  • Считать setImmediate и setTimeout(fn, 0) эквивалентными — порядок отличается.

Дополнительные вопросы

  • Чем setImmediate отличается от setTimeout(0)?
  • Что такое libuv?

Что такое require vs import в Node.js?

Middle

require — CommonJS, синхронный, runtime resolve. import — ESM, статический parse-time, асинхронный.

В одном проекте смешивание возможно через type: module + .cjs/.mjs или через interop в CommonJS.

Из практики: новые проекты — ESM. Старые на CJS живут долго. import (default) работает иначе с CJS-экспортами — иногда нужен import * as.

Частые ошибки

  • Смешивать require и import в одном файле без понимания.
  • Полагать, что named exports CJS всегда работают.

Дополнительные вопросы

  • Что такое top-level await?
  • Чем .mjs отличается от .cjs?

Что такое CSS-in-JS и какие подходы есть?

Middle

CSS-in-JS — стили пишутся в JS: styled-components, emotion, JSS, vanilla-extract. Преимущества: scoped по умолчанию, динамические стили, отсутствие class-name коллизий.

Минусы: размер bundle, runtime cost (для runtime-решений), сложность server-side rendering.

Из практики: для React-проектов — частый выбор. Сейчас тренд на zero-runtime (vanilla-extract, linaria) — компилируются в обычный CSS.

Частые ошибки

  • Использовать runtime CSS-in-JS в высоконагруженных компонентах без оптимизации.
  • Не настраивать SSR-стримминг стилей.

Дополнительные вопросы

  • Что такое CSS Modules?
  • Чем vanilla-extract отличается от styled-components?

Что такое hot module replacement (HMR)?

Middle

HMR — механизм, при котором в dev-режиме изменения в коде применяются без перезагрузки страницы. Vite, Webpack, Parcel поддерживают.

Frontend-фреймворки (React, Vue) интегрируются с HMR через специальные плагины — сохраняют состояние компонентов.

Из практики: HMR радикально ускоряет разработку. В Vite работает быстрее, чем в Webpack за счёт ESM-нативного подхода.

Частые ошибки

  • Считать, что HMR работает в production — это только dev.
  • Не учитывать, что HMR не сохраняет некоторые состояния (модальные окна, локальные стейты).

Дополнительные вопросы

  • Чем Vite быстрее Webpack для HMR?
  • Что такое Fast Refresh в React?

Что такое source maps?

Middle

Source map — файл, сопоставляющий minified-код с исходным. Браузер использует для отладки: показывает оригинальный код, не минифицированный.

В production обычно генерируют source maps, но не публикуют их (или публикуют на отдельном защищённом адресе).

Из практики: для отчётов об ошибках (Sentry) загружают source maps в систему мониторинга — стектрейсы становятся читаемыми.

Частые ошибки

  • Опубликовать source maps в production без защиты — раскрытие кода.
  • Не загружать source maps в систему мониторинга — нечитаемые трейсы.

Дополнительные вопросы

  • Что такое source map versioning?
  • Чем inline source map отличается от внешнего?

Что такое npm audit и как с ним работать?

Middle

npm audit сканирует package-lock.json на известные уязвимости (CVE) и предлагает фиксы.

Уровни: low, moderate, high, critical. npm audit fix пытается обновить пакеты автоматически. Иногда нужен npm audit fix --force (риск breaking changes).

Из практики: подключают в CI как блокирующий шаг на high/critical. Также Renovate/Dependabot создают PR с обновлениями.

Частые ошибки

  • Игнорировать moderate-вrning — иногда они важны.
  • Использовать --force без проверки.

Дополнительные вопросы

  • Чем npm audit отличается от snyk?
  • Как настроить audit в CI?