Перейти к основному содержимому

Mock Interview c подписчками на Junior Frontend позицию

· 33 мин. чтения

Сегодня мы разберем запись прямого эфира, посвященного проведению собеседования на позицию Junior-разработчика. В ходе трансляции кандидаты активно отвечали на вопросы по теории JavaScript, а также решали практические задачи на понимание работы кода. Мероприятие прошло в интерактивном формате, где участники могли не только проверить свои знания, но и получить развернутую обратную связь от опытных разработчиков.

Вопрос 1. Что такое встроенные (нативные) объекты в JavaScript и чем они отличаются от host-объектов?

Таймкод: 00:08:33

Ответ собеседника: Неполный. Участник упомянул, что host-объекты зависят от среды исполнения (например, window, document в браузере), а встроенные объекты — это Object, Array, String и т.д. Однако полного определения встроенных объектов не дано, и различие между ними раскрыто недостаточно.

Правильный ответ:

Встроенные (нативные) объекты (Native Objects) — это объекты, которые являются частью самого языка JavaScript и определены в спецификации ECMAScript. Они доступны независимо от среды выполнения (браузер, Node.js, Deno и т.д.) и всегда присутствуют в любом корректном движке JavaScript.

К встроенным объектам относятся:

  • Типы-обёртки: Object, Number, String, Boolean, Symbol, BigInt
  • Коллекции: Array, Map, Set, WeakMap, WeakSet
  • Утилиты: Math, Date, RegExp, JSON, Reflect, Proxy
  • Обработка ошибок: Error, TypeError, RangeError, SyntaxError и другие
  • Асинхронность: Promise
  • Функции: Function
  • Другие: ArrayBuffer, DataView, типизированные массивы (Int8Array, Float64Array и т.д.)

Эти объекты стандартизированы спецификацией ECMAScript и гарантированно существуют в любой среде, поддерживающей JavaScript.

Host-объекты (Объекты среды) — это объекты, которые предоставляются конкретной средой выполнения (host environment). Они не являются частью спецификации ECMASект, а добавляются окружением, в котором работает JavaScript.

Примеры host-объектов:

  • В браузере: window, document, navigator, location, history, console, HTMLElement, XMLHttpRequest, fetch, localStorage, sessionStorage, Event, setTimeout, setInterval
  • В Node.js: process, require, module, exports, Buffer, fs, http, __dirname, __filename

Ключевые отличия:

  1. Стандартизация: Нативные объекты определены в спецификации ECMAScript и ведут себя одинаково во всех средах. Host-объекты могут различаться между браузерами или отсутствовать в других средах (например, document не существует в Node.js).

  2. Доступность: Нативные объекты всегда доступны. Host-объекты зависят от среды и могут быть недоступны.

  3. Поведение: Нативные объекты следуют строгим правилам спецификации. Host-объекты могут иметь специфичное поведение, не описанное в стандарте (например, host-объекты могут нарушать некоторые правила, такие как запрет на удаление свойств).

  4. Наследование: Нативные объекты имеют предсказуемую цепочку прототипов. Host-объекты могут иметь произвольную или даже пустую цепочку прототипов.

Практический пример:

// Нативный объект — работает везде
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true

// Host-объект — работает только в браузере
console.log(typeof document); // "object" в браузере, "undefined" в Node.js
console.log(typeof window); // "object" в браузере, "undefined" в Node.js

// Ещё пример: в Node.js есть Buffer, но в браузере его нет (по умолчанию)
console.log(typeof Buffer); // "function" в Node.js, "undefined" в браузере

Важно понимать: разграничение между нативными и host-объектами влияет на то, как вы пишите кроссплатформенный код. Если ваш код использует host-объекты, он будет работать только в конкретной среде. Использование нативных объектов обеспечивает переносимость.

Вопрос 2. Какие глобальные встроенные объекты в JavaScript вы знаете?

Таймкод: 00:13:02

Ответ собеседника: Неполный. Участник упомянул window (который является host-объектом, а не встроенным), но не смог перечислить нативные глобальные объекты. Ответ оборвался и не содержал полного списка.

Правильный ответ:

Глобальные встроенные объекты JavaScript — это объекты, доступные глобально в любом месте кода без необходимости их импортировать или создавать явно. Они являются частью спецификации ECMAScript и доступны как в браузере, так и в Node.js.

Типы-обёртки примитивов:

  • Object — базовый объект, основа объектной модели
  • String — работа со строками
  • Number — работа с числами
  • Boolean — работа с логическими значениями
  • Symbol — уникальные идентификаторы (ES6)
  • BigInt — работа с целыми числами произвольной точности (ES2020)

Коллекции:

  • Array — упорядоченные коллекции
  • Map — коллекция пар ключ-значение с любыми ключами
  • Set — коллекция уникальных значений
  • WeakMap — коллекция пар ключ-значение со слабыми ссылками на ключи
  • WeakSet — коллекция уникальных объектов со слабыми ссылками
  • ArrayBuffer — буфер бинарных данных
  • DataView — представление для чтения/записи в ArrayBuffer

Типизированные массивы (TypedArray):

  • Int8Array, Uint8Array, Uint8ClampedArray
  • Int16Array, Uint16Array
  • Int32Array, Uint32Array
  • Float32Array, Float64Array
  • BigInt64Array, BigUint64Array

Утилиты:

  • Math — математические константы и функции
  • Date — работа с датой и временем
  • RegExp — регулярные выражения
  • JSON — сериализация и десериализация JSON
  • Reflect — метаданные и рефлексия (ES6)
  • Proxy — перехват операций над объектами (ES6)
  • Intl — интернационализация (форматирование дат, чисел, строк)

Обработка ошибок:

  • Error — базовый объект ошибки
  • TypeError, RangeError, SyntaxError, ReferenceError, URIError, EvalError, AggregateError

Асинхронность:

  • Promise — работа с асинхронными операциями (ES6)

Функции:

  • Function — конструктор функций

Другие:

  • globalThis — универсальная ссылка на глобальный объект (ES2020)
  • NaN, Infinity, undefined — глобальные свойства (не объекты, но глобальные значения)

Примеры использования:

// Типы-обёртки
const str = String(42); // "42"
const num = Number("3.14"); // 3.14
const bool = Boolean(1); // true
const sym = Symbol("description"); // Symbol(description)
const big = BigInt(9007199254740991); // 9007199254740991n

// Коллекции
const map = new Map([["key", "value"]]);
const set = new Set([1, 2, 3]);

// Утилиты
Math.random(); // случайное число
new Date(); // текущая дата
JSON.stringify({a: 1}); // '{"a":1}'
/regex/.test("test"); // true

// Ошибки
throw new TypeError("Неверный тип");

// Асинхронность
Promise.resolve(42).then(v => console.log(v));

// Типизированные массивы
const buffer = new ArrayBuffer(16);
const view = new Int32Array(buffer);

Важное замечание: Обратите внимание, что window, document, console, navigator — это host-объекты (предоставляются средой браузера), а не встроенные объекты языка. Они не входят в спецификацию ECMAScript и недоступны в других средах (например, в Node.js). В Node.js аналогом window является global, а начиная с ES2020 — globalThis, который работает везде.

Вопрос 3. Что делает оператор typeof и какие особенности он имеет?

Таймкод: 00:18:09

Ответ собеседника: Неполный. Участники упомянули, что typeof возвращает тип значения, а ведущий дополнил про typeof null === 'object' и typeof function === 'function'. Однако не был дан полный перечень возвращаемых значений и не раскрыты все особенности оператора.

Правильный ответ:

Оператор typeof — это унарный оператор в JavaScript, который возвращает строку, указывающую тип операнда. Он является основным средством определения типа значения во время выполнения.

Синтаксис:

typeof operand
typeof(operand) // скобки не делают его функцией, это просто группировка

Полная таблица возвращаемых значений:

ОперандРезультатПример
Undefined"undefined"typeof undefined
Null"object"typeof null
Boolean"boolean"typeof true
Number"number"typeof 42, typeof NaN
BigInt"bigint"typeof 1n
String"string"typeof "hello"
Symbol"symbol"typeof Symbol()
Function"function"typeof function(){}
Любой другой объект"object"typeof {}, typeof [], typeof new Date()

Подробный разбор каждого случая:

1. Undefined

typeof undefined; // "undefined"
let x;
typeof x; // "undefined"
typeof undeclaredVar; // "undefined" (не вызывает ReferenceError!)

2. Null — исторический баг

typeof null; // "object"

Это известный баг в языке, который сохраняется ради обратной совместимости. В ранних реализациях JavaScript значения хранились в 32-битных ячейках, где младшие биты служили тегом типа. Объекты имели тег 000, и null представлялся как нулевой указатель (0x00000000), что совпадало с тегом объекта. Исправить это нельзя — сломается огромное количество существующего кода.

3. Boolean

typeof true; // "boolean"
typeof false; // "boolean"

4. Number и особые числовые значения

typeof 42; // "number"
typeof 3.14; // "number"
typeof NaN; // "number" — NaN технически является числом
typeof Infinity; // "number"
typeof -Infinity; // "number"
typeof 0; // "number"

5. BigInt (ES2020)

typeof 1n; // "bigint"
typeof BigInt(1); // "bigint"

6. String

typeof ""; // "string"
typeof "hello"; // "string"
typeof `template`; // "string"

7. Symbol (ES6)

typeof Symbol(); // "symbol"
typeof Symbol("id"); // "symbol"

8. Function — особый случай

typeof function() {}; // "function"
typeof class {}; // "function"
typeof (() => {}); // "function"
typeof Math.sin; // "function"
typeof new Function(); // "function"

Хотя функции являются объектами (наследуют от Function.prototype), typeof для них возвращает "function". Это единственное исключение из правила, что все объекты возвращают "object". Это сделано для удобства — функции имеют внутренний метод [[Call]], и typeof проверяет его наличие.

9. Все остальные объекты

typeof {}; // "object"
typeof []; // "object" — массивы не выделяются отдельно!
typeof new Date(); // "object"
typeof /regex/; // "object"
typeof new Map(); // "object"
typeof new Set(); // "object"
typeof new WeakMap(); // "object"
typeof new Error(); // "object"
typeof new Promise(() => {}); // "object"
typeof null; // "object" — баг

Ключевые особенности и подводные камни:

A. Безопасная проверка необъявленных переменных

// Это вызовет ReferenceError
undeclaredVariable; // ReferenceError: undeclaredVariable is not defined

// А это — нет, вернёт "undefined"
typeof undeclaredVariable; // "undefined"

Это единственный оператор, который не выбрасывает ошибку при обращении к необъявленной переменной. Используется для feature detection:

if (typeof SomeGlobal !== "undefined") {
// Безопасное использование опциональной глобальной переменной
}

B. Невозможность отличить массив от объекта

typeof []; // "object"
typeof {}; // "object"

Для проверки массива используйте Array.isArray():

Array.isArray([]); // true
Array.isArray({}); // false

C. NaN имеет тип "number"

typeof NaN; // "number"

Для проверки на NaN используйте Number.isNaN() (а не глобальный isNaN(), который приводит аргумент к числу):

Number.isNaN(NaN); // true
Number.isNaN("hello"); // false (не приводит к числу)
isNaN("hello"); // true (сначала приводит к числу, получает NaN)

D. Разница с instanceof

typeof "hello"; // "string"
"hello" instanceof String; // false — примитив не является экземпляром

typeof new String("hello"); // "object" — объект-обёртка
new String("hello") instanceof String; // true

E. Область видимости и TDZ (Temporal Dead Zone)

typeof x; // ReferenceError: Cannot access 'x' before initialization
let x = 5;

typeof y; // ReferenceError: Cannot access 'y' before initialization
const y = 10;

Переменные, объявленные через let и const, находятся в TDZ до инициализации, и typeof выбросит ошибку при обращении к ним до объявления (в отличие от var и необъявленных переменных).

Практические рекомендации:

Для более точной проверки типов используйте комбинацию подходов:

// Универсальная функция для получения точного типа
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}

getType(null); // "null"
getType([]); // "array"
getType({}); // "object"
getType(undefined); // "undefined"
getType(42); // "number"
getType("hello"); // "string"
getType(() => {}); // "function"

Итог: typeof — быстрый и безопасный способ определить базовый тип значения, но он имеет ограничения: не отличает массивы от объектов, возвращает "object" для null и "function" для функций. Для более точной типизации используйте Array.isArray(), instanceof, Object.prototype.toString.call() и другие методы.

Вопрос 4. Что выведет данный код с console.log (задача на понимание hoisting, typeof, ReferenceError)?

Таймкод: 00:25:22

Ответ собеседника: Неполный. Участник начал разбор кода по строкам, но ответ был сбивчивым. Он упомянул undefined, string, ReferenceError, но не дал полного последовательного разбора. Ведущий подтвердил, что код упадёт с ReferenceError.

Правильный ответ:

Поскольку сам код в вопросе не приведён, разберу типовые паттерны задач на hoisting, typeof и ReferenceError, которые часто встречаются на собеседованиях. Эти паттерны охватывают все ключевые концепции, упомянутые в вопросе.

Паттерн 1: typeof и необъявленные переменные

console.log(typeof undeclaredVar); // "undefined" — не вызывает ошибку!
console.log(undeclaredVar); // ReferenceError: undeclaredVar is not defined

typeof — единственный оператор, который безопасно работает с необъявленными переменными. Он возвращает "undefined" вместо выброса ReferenceError.

Паттерн 2: var, let, const и Temporal Dead Zone (TDZ)

// var — hoisting с инициализацией undefined
console.log(a); // undefined (не ошибка!)
var a = 5;

// let — hoisting без инициализацией, TDZ
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

// const — аналогично let
console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 15;

Ключевое различие: var поднимается (hoisting) и инициализируется значением undefined, поэтому обращение до объявления возвращает undefined. let и const тоже поднимаются, но остаются в «мёртвой зоне» (TDZ) до строки объявления — любое обращение вызывает ReferenceError.

Паттерн 3: typeof в TDZ

console.log(typeof d); // ReferenceError: Cannot access 'd' before initialization
let d = 20;

console.log(typeof e); // ReferenceError: Cannot access 'e' before initialization
const e = 25;

Это важный нюанс: typeof не защищает от ReferenceError для переменных, объявленных через let и const в TDZ. Защита работает только для необъявленных переменных (которые нигде не описаны через var, let, const).

Паттерн 4: Функции и hoisting

// Function Declaration — полностью поднимается
foo(); // "Hello from foo"
function foo() {
console.log("Hello from foo");
}

// Function Expression с var — переменная поднимается как undefined
bar(); // TypeError: bar is not a function
var bar = function() {
console.log("Hello from bar");
};

// Function Expression с let — TDZ
baz(); // ReferenceError: Cannot access 'baz' before initialization
let baz = function() {
console.log("Hello from baz");
};

Паттерн 5: typeof null и массивов

console.log(typeof null); // "object" — исторический баг
console.log(typeof []); // "object" — массив не выделяется отдельно
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function" — исключение из правила

Паттерн 6: Типичная задача с последовательным выполнением

console.log(typeof x); // "undefined" (x не объявлена нигде)
console.log(typeof y); // ReferenceError — выполнение прерывается здесь!

var y = 10;
console.log(typeof z); // эта строка никогда не выполнится

Если в коде переменная y объявлена через let или const ниже по коду, то typeof y вызовет ReferenceError, потому что y находится в TDZ. Выполнение скрипта прервётся, и последующие console.log не выполнятся.

Паттерн 7: Сложная задача с var и функциями

console.log(a); // undefined (var поднят и инициализирован undefined)
console.log(typeof b); // "undefined" (b не объявлена)

var a = 1;

console.log(a); // 1
console.log(typeof a); // "number"

console.log(c()); // TypeError: c is not a function (c = undefined)
var c = function() { return 42; };

Ключевые правила для решения таких задач:

  1. Определите, как объявлена переменная:

    • Не объявлена нигде → typeof вернёт "undefined", прямое обращение → ReferenceError
    • var → hoisting с undefined, обращение до объявления → undefined
    • let/const → hoisting без инициализации, обращение до объявления → ReferenceError (включая typeof!)
  2. Помните про исключения typeof:

    • typeof null === "object" (баг)
    • typeof function(){} === "function" (хотя функции — объекты)
  3. Код выполняется последовательно: если на какой-то строке возникает непойманная ошибка, выполнение прерывается, и последующие строки не выполняются.

  4. Function Declaration vs Function Expression:

    • function foo(){} — полностью поднимается, можно вызывать до объявления
    • var foo = function(){} — поднимается как undefined, вызов до объявления → TypeError
    • let foo = function(){} — TDZ, вызов до объявления → ReferenceError

Практический алгоритм решения:

При разборе такого кода на собеседовании:

  • Пройдитесь по каждой console.log сверху вниз
  • Для каждого обращения к переменной определите: объявлена ли она, как объявлена, инициализирована ли она к этому моменту
  • Если встречаете непойманную ошибку — остановитесь, дальнейший код не выполняется
  • Для typeof проверьте: не находится ли переменная в TDZ (тогда будет ReferenceError, а не "undefined")

Вопрос 5. Что выведет данный код с операторами typeof, скобками и конкатенацией (задача на приоритет операторов и приведение типов)?

Таймкод: 00:34:10

Ответ собеседника: Неполный. Участники частично угадали результат, но не смогли полностью разобрать все строки кода. Ведущий пояснил про оператор запятой в скобках и приоритет typeof, но полного разбора всех строк не было.

Правильный ответ:

Поскольку сам код в вопросе не приведён, разберу типовые паттерны задач на приоритет операторов, typeof, оператор запятой и конкатенацию — именно эти темы упомянуты в вопросе. Разбор будет достаточно подробным, чтобы покрыть любой вариант такой задачи.

Паттерн 1: Оператор запятой в скобках

Оператор запятой , вычисляет каждый операнд слева направо и возвращает значение последнего операнда. Приоритет — самый низкий из всех операторов JavaScript.

let x = (1, 2, 3);
console.log(x); // 3 — возвращается последнее значение

let y = (1 + 2, 3 + 4, 5 + 6);
console.log(y); // 11 — (3, 7, 11) → 11

// Скобки заставляют вычислить всё выражение как единое целое
let z = (console.log("first"), console.log("second"), "third");
// Выведет: "first", "second"
console.log(z); // "third"

Паттерн 2: Приоритет typeof

typeof — унарный оператор с высоким приоритетом (наравне с другими унарными: !, ~, +, -, ++, --, delete, void). Он привязывается к ближайшему операнду справа.

typeof 3 + " " + typeof "hello"
// Вычисляется как: (typeof 3) + " " + (typeof "hello")
// Результат: "number" + " " + "string" = "number string"

// Важно: typeof не требует скобок, но с ними — тоже работает
typeof(3) // "number" — скобки здесь не функция, а группировка

Паттерн 3: Конкатенация строк и приведение типов

Оператор + при наличии хотя бы одной строки выполняет конкатенацию, приводя другие операнды к строкам.

console.log(typeof 3 + " " + typeof "hello");
// typeof 3 → "number"
// typeof "hello" → "string"
// "number" + " " + "string" → "number string"

console.log(typeof true + typeof 42);
// "boolean" + "number" → "booleannumber" (без пробела!)

console.log(typeof null + " " + typeof undefined);
// "object" + " " + "undefined" → "object undefined"

Паттерн 4: Комбинированная задача

console.log(typeof (1, 2, 3));
// Скобки с запятой: (1, 2, 3) → 3
// typeof 3 → "number"

console.log(typeof (1, "hello", true));
// (1, "hello", true) → true
// typeof true → "boolean"

console.log(typeof (1 + 2, "abc", 3.14));
// (3, "abc", 3.14) → 3.14
// typeof 3.14 → "number"

Паттерн 5: Сложная задача с несколькими console.log

console.log(typeof 3); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof 3 + " " + typeof "hello"); // "number string"

Разбор по шагам:

  1. typeof 3"number" (унарный оператор, высокий приоритет)
  2. typeof "hello""string"
  3. "number" + " ""number " (конкатенация строк)
  4. "number " + "string""number string" (конкатенация строк)

Паттерн 6: typeof с выражениями

console.log(typeof 1 + 2); // "number2" — (typeof 1) + 2 → "number" + 2
console.log(typeof (1 + 2)); // "number" — typeof (3) → "number"

console.log(typeof 1 + "2"); // "number2" — (typeof 1) + "2"
console.log(typeof (1 + "2")); // "string" — typeof "12" → "string"

Паттерн 7: Оператор запятой в сочетании с typeof

// Типичная задача на собеседовании:
console.log(typeof (1, 2, 3)); // "number" — запятые возвращают 3, typeof 3 → "number"

console.log(typeof (1, "abc")); // "string" — запятые возвращают "abc", typeof "abc" → "string"

// Более сложный вариант:
console.log(typeof (() => {}, 42)); // "number" — запятые возвращают 42

Полная таблица приоритетов операторов (релевантная для задачи):

ПриоритетОператорОписание
Высокийtypeof, !, ~, +(унарный), -(унарный)Унарные операторы
...*, /, %Мультипликативные
...+, -Аддитивные (включая конкатенацию)
...,Оператор запятой (самый низкий)

Практический алгоритм решения таких задач:

  1. Найдите скобки — они задают порядок вычисления
  2. Внутри скобок с запятыми — запомните, что возвращается последнее значение
  3. Определите typeof — он применяется к результату выражения справа
  4. Оператор + — если хотя бы один операнд строка, происходит конкатенация
  5. Выполняйте слева направо — при равном приоритете

Пример полного разбора:

// Допустим, код выглядит так:
console.log(typeof 3); // Строка 1
console.log(typeof "hello"); // Строка 2
console.log(typeof 3 + " " + typeof "hello"); // Строка 3

Разбор:

  • Строка 1: typeof 3"number" → вывод: number
  • Строка 2: typeof "hello""string" → вывод: string
  • Строка 3: (typeof 3) + " " + (typeof "hello")"number" + " " + "string""number string" → вывод: number string

Ключевые правила для запоминания:

  1. typeof имеет высокий приоритет и привязывается к ближайшему операнду справа
  2. Оператор запятой , возвращает последнее значение из списка
  3. Оператор + с хотя бы одной строкой выполняет конкатенацию
  4. Скобки () вокруг выражения с запятыми заставляют вычислить всё выражение целиком перед применением внешнего оператора
  5. typeof не является функцией — скобки после него не обязательны и не меняют поведения

Вопрос 6. Что такое методы call и apply, для чего они используются, в чём их различие и особенности?

Таймкод: 00:42:17

Ответ собеседника: Неполный. Участник верно описал базовое различие (call — аргументы через запятую, apply — массивом), упомянул про null/undefined и строгий режим. Ведущий дополнил про ограничение на количество аргументов в apply. Однако не были раскрыты: связь с bind, паттерн заимствования методов, использование с псевдомассивами, современная альтернатива через spread-оператор, практические примеры.

Правильный ответ:

Методы call и apply — это методы экземпляров Function, которые позволяют явно задать значение this при вызове функции и сразу её выполнить. Они являются основными инструментами для управления контекстом выполнения функции в JavaScript.

Синтаксис:

func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [arg1, arg2, ...])

Параметры:

  • thisArg — значение, которое будет использовано как this внутри функции
  • Оставшиеся параметры — аргументы, передаваемые в функцию

Ключевое различие:

Единственное различие — в формате передачи аргументов:

  • call принимает аргументы списком через запятую
  • apply принимает аргументы массивом (или псевдомассивом)
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: "Алексей" };

// call — аргументы через запятую
greet.call(user, "Привет", "!"); // "Привет, Алексей!"

// apply — аргументы массивом
greet.apply(user, ["Здравствуй", "."]); // "Здравствуй, Алексей."

Поведение thisArg:

Значение thisArg обрабатывается по-разному в строгом и нестрогом режиме:

function showThis() {
console.log(this);
}

// Нестрогий режим
showThis.call(null); // window (глобальный объект)
showThis.call(undefined); // window
showThis.call(42); // Number {42} — примитив оборачивается в объект

// Строгий режим ("use strict")
function showThisStrict() {
"use strict";
console.log(this);
}
showThisStrict.call(null); // null — передаётся как есть
showThisStrict.call(undefined); // undefined — передаётся как есть
showThisStrict.call(42); // 42 — примитив без обёртки

Основные сценарии использования:

1. Вызов функции с явным контекстом

function introduce() {
console.log(`Меня зовут ${this.name}, мне ${this.age} лет`);
}

const person = { name: "Мария", age: 28 };
introduce.call(person); // "Меня зовут Мария, мне 28 лет"

2. Заимствование методов (Method Borrowing)

Один из самых распространённых паттернов — использование методов одного объекта для другого:

const cat = { name: "Барсик", color: "рыжий" };
const dog = { name: "Шарик", color: "чёрный" };

function describe() {
return `${this.name}${this.color} питомец`;
}

// Заимствуем функцию и вызываем с разным контекстом
console.log(describe.call(cat)); // "Барсик — рыжий питомец"
console.log(describe.call(dog)); // "Шарик — чёрный питомец"

3. Работа с псевдомассивами (Array-like objects)

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

// Преобразование arguments в массив (до ES6)
function sum() {
const args = Array.prototype.slice.call(arguments);
return args.reduce((acc, val) => acc + val, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

// Передача arguments другой функции
function wrapper() {
return Math.max.apply(null, arguments);
}
console.log(wrapper(3, 7, 2, 9)); // 9

// Работа с NodeList в DOM
const divs = document.querySelectorAll("div");
const divArray = Array.prototype.slice.call(divs);

4. Максимизация/минимизация массива

const numbers = [5, 6, 1, 9, 3];

// Без apply пришлось бы использовать цикл
const max = Math.max.apply(null, numbers); // 9
const min = Math.min.apply(null, numbers); // 1

// Современная альтернатива через spread (ES6+)
const maxModern = Math.max(...numbers); // 9

5. Композиция массивов

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Добавить все элементы arr2 в arr1
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]

// Современная альтернатива
arr1.push(...arr2);

Ограничение apply на количество аргументов:

apply имеет техническое ограничение на количество аргументов — размер стека вызовов ограничен (обычно около 65 000–125 000 элементов в зависимости от движка). Передача слишком большого массива вызовет RangeError: Maximum call stack size exceeded.

// Проблема с apply и большим массивом
const hugeArray = new Array(200000).fill(1);

try {
Math.max.apply(null, hugeArray); // RangeError!
} catch (e) {
console.log(e.message); // "Maximum call stack size exceeded"
}

// Решение через spread тоже может упадёт при очень больших массивах
// Лучшее решение — reduce
const max = hugeArray.reduce((a, b) => Math.max(a, b), -Infinity);
console.log(max); // 1

Сравнение с bind:

const user = { name: "Олег" };

function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}

// call/apply — вызывают функцию сразу
greet.call(user, "Привет"); // сразу выводит "Привет, Олег!"
greet.apply(user, ["Здравствуй"]); // сразу выводит "Здравствуй, Олег!"

// bind — возвращает новую функцию с привязанным контекстом
const boundGreet = greet.bind(user, "Добрый день");
boundGreet(); // "Добрый день, Олег!" — вызов отложен

// bind позволяет частично применить аргументы (currying)
const sayHelloToOleg = greet.bind(user, "Привет");
sayHelloToOleg(); // "Привет, Олег!"

Современные альтернативы (ES6+):

const user = { name: "Анна" };

function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

// Вместо call — spread-оператор
greet.call(user, "Привет", "!");
// Эквивалент (но без управления this):
// greet("Привет", "!") — не подходит, нужен this

// Вместо apply — spread-оператор
const args = ["Здравствуй", "."];
greet.apply(user, args);
// Современный эквивалент:
greet.call(user, ...args); // или если функция не нуждается в this:
// greet(...args)

// Вместо Array.prototype.slice.call(arguments)
const argsArray = [...arguments]; // или Array.from(arguments)

Когда использовать что:

  • call — когда количество аргументов известно и невелико
  • apply — когда аргументы уже собраны в массив или псевдомассив (но массив не слишком большой)
  • bind — когда нужно создать функцию с привязанным контекстом для отложенного вызова (обработчики событий, коллбэки)
  • Spread-оператор ... — современная альтернатива apply для распаковки массива в аргументы

Практический пример — создание универсального декоратора:

// Декоратор для логирования вызовов
function withLogging(fn) {
return function(...args) {
console.log(`Вызов ${fn.name} с аргументами:`, args);
const result = fn.apply(this, args);
console.log(`Результат ${fn.name}:`, result);
return result;
};
}

const add = withLogging(function(a, b) {
return a + b;
});

add(3, 5);
// Вызов с аргументами: [3, 5]
// Результат: 8

Итог: call и apply — фундаментальные методы для управления контекстом this в JavaScript. Они незаменимы при заимствовании методов, работе с псевдомассивами и создании гибких функциональных абстракций. В современном коде call часто заменяется на spread-оператор, а bind используется для отложенной привязки контекста, но понимание всех трёх методов критически важно для работы с legacy-кодом и глубокого понимания JavaScript.

Вопрос 7. Что такое Function.prototype.bind, как он работает и в чём его особенности?

Таймкод: 00:48:06

Ответ собеседника: Неполный. Участники упомянули, что bind привязывает контекст и возвращает новую функцию (в отличие от call/apply), а ведущий дополнил про частичное применение аргументов. Однако не были раскрыты: особенности повторного биндинга, работа с конструкторами, потеря привязки при деструктуризации, практические паттерны использования, сравнение с стрелочными функциями.

Правильный ответ:

Function.prototype.bind — это метод, который создаёт новую функцию с привязанным контекстом this и, опционально, с частично применёнными аргументами. В отличие от call и apply, bind не вызывает функцию сразу, а возвращает новую функцию-обёртку для последующего вызова.

Синтаксис:

const boundFunc = func.bind(thisArg, arg1, arg2, ...)

Параметры:

  • thisArg — значение this для привязки
  • arg1, arg2, ... — аргументы для частичного применения (partial application)

Ключевое отличие от call/apply:

const user = { name: "Дмитрий" };

function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

// call/apply — вызывают функцию СРАЗУ
greet.call(user, "Привет", "!"); // сразу выводит "Привет, Дмитрий!"
greet.apply(user, ["Здравствуй", "."]); // сразу выводит "Здравствуй, Дмитрий."

// bind — возвращает НОВУЮ ФУНКЦИЮ, вызова не происходит
const boundGreet = greet.bind(user, "Добрый день");
// Пока ничего не выведено!
boundGreet("!"); // "Добрый день, Дмитрий!" — вызов происходит здесь

Частичное применение аргументов (Partial Application):

function multiply(a, b) {
return a * b;
}

// Фиксируем первый аргумент
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);

console.log(double(5)); // 10 (2 * 5)
console.log(triple(5)); // 15 (3 * 5)
console.log(double(10)); // 20 (2 * 10)

Поведение thisArg (аналогично call/apply):

function showThis() {
console.log(this);
}

// Нестрогий режим
showThis.bind(null)(); // window
showThis.bind(undefined)(); // window
showThis.bind(42)(); // Number {42}

// Строгий режим
function showThisStrict() {
"use strict";
console.log(this);
}
showThisStrict.bind(null)(); // null
showThisStrict.bind(undefined)(); // undefined

Ключевые особенности bind:

1. Повторный bind не перезаписывает контекст

Одна из самых важных и часто проверяемых особенностей — контекст, привязанный через bind, нельзя изменить повторным вызовом bind, call или apply.

function showName() {
console.log(this.name);
}

const obj1 = { name: "Объект 1" };
const obj2 = { name: "Объект 2" };

const boundToShowName = showName.bind(obj1);

// Пытаемся перезаписать контекст — не работает!
boundToShowName.call(obj2); // "Объект 1" — контекст остался obj1
boundToShowName.apply(obj2); // "Объект 1"
boundToShowName.bind(obj2)(); // "Объект 1"

// Вывод: bind создаёт "жёсткую" привязку, которую нельзя переопределить

2. bind с конструкторами (new)

Если функция, созданная через bind, вызывается как конструктор (с new), то привязанный thisArg игнорируется, и создаётся новый объект.

function Person(name, age) {
this.name = name;
this.age = age;
}

const BoundPerson = Person.bind(null, "Аноним");

// Вызов через new — привязанный this (null) игнорируется
const person = new BoundPerson(25);
console.log(person.name); // "Аноним" — привязанный аргумент работает
console.log(person.age); // 25
console.log(person instanceof Person); // true

3. Потеря привязки при деструктуризации

Это частая ошибка при работе с методами классов:

class Counter {
constructor() {
this.count = 0;
}

increment() {
this.count++;
console.log(this.count);
}
}

const counter = new Counter();

// Прямой вызов — работает
counter.increment(); // 1

// Деструктуризация — контекст теряется!
const { increment } = counter;
increment(); // TypeError: Cannot read properties of undefined (reading 'count')
// this стал undefined (строгий режим) или window (нестрогий)

// Решение 1: bind в конструкторе
class CounterFixed {
constructor() {
this.count = 0;
this.increment = this.increment.bind(this); // привязываем в конструкторе
}

increment() {
this.count++;
console.log(this.count);
}
}

const counter2 = new CounterFixed();
const { increment: inc } = counter2;
inc(); // 1 — контекст сохранён!

// Решение 2: стрелочная функция (свойство-стрелка)
class CounterArrow {
constructor() {
this.count = 0;
}

// Стрелочная функция захватывает this из конструктора
increment = () => {
this.count++;
console.log(this.count);
};
}

const counter3 = new CounterArrow();
const { increment: inc2 } = counter3;
inc2(); // 1 — контекст сохранён автоматически!

4. bind и стрелочные функции

Стрелочные функции не имеют собственного this и не могут быть привязаны через bind:

const obj = { name: "Тест" };

const arrowFunc = () => {
console.log(this.name);
};

const boundArrow = arrowFunc.bind(obj);
boundArrow(); // this остаётся таким же, каким был при создании стрелки
// bind, call, apply не работают со стрелочными функциями!

Практические паттерны использования:

Паттерн 1: Обработчики событий

class Button {
constructor(element) {
this.element = element;
this.clickCount = 0;

// Без bind — this будет указывать на DOM-элемент
// this.element.addEventListener("click", this.handleClick); // ОШИБКА!

// С bind — this остаётся экземпляром Button
this.element.addEventListener("click", this.handleClick.bind(this));

// Или через стрелочную функцию:
// this.element.addEventListener("click", () => this.handleClick());
}

handleClick() {
this.clickCount++;
console.log(`Клик #${this.clickCount}`);
}
}

Паттерн 2: Карринг (Currying)

// Каррирование через bind
function add(a, b, c) {
return a + b + c;
}

const addFive = add.bind(null, 5);
const addFiveAndThree = addFive.bind(null, 3);

console.log(addFiveAndThree(2)); // 10 (5 + 3 + 2)
console.log(addFive(10, 20)); // 35 (5 + 10 + 20)

// Более элегантное карринг через замыкание
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return curried.bind(this, ...args);
};
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

Паттерн 3: Дебаунс и троттлинг

function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args); // сохраняем контекст и аргументы
}, delay);
};
}

class SearchInput {
constructor() {
this.query = "";
// bind не нужен, если используем стрелку или замыкание
this.handleInput = debounce(this.handleInput.bind(this), 300);
}

handleInput(event) {
this.query = event.target.value;
console.log(`Поиск: ${this.query}`);
}
}

Паттерн 4: Создание специализированных функций

// Базовая функция логирования
function log(level, message, timestamp) {
console.log(`[${timestamp}] [${level}] ${message}`);
}

// Создаём специализированные функции через bind
const logInfo = log.bind(null, "INFO");
const logError = log.bind(null, "ERROR");
const logWithTimestamp = (level, message) => log(level, message, new Date().toISOString());

logInfo("Сервер запущен"); // "[undefined] [INFO] Сервер запущен"
logError("Ошибка подключения"); // "[undefined] [ERROR] Ошибка подключения"
logWithTimestamp("INFO", "Тест"); // "[2024-01-15T...] [INFO] Тест"

Сравнение подходов для привязки контекста:

class Example {
constructor() {
this.value = 42;
}

// Подход 1: bind в конструкторе (классический)
method1() {
return this.value;
}

// Подход 2: стрелочная функция как свойство (современный)
method2 = () => {
return this.value;
};

// Подход 3: bind при передаче (ручной)
// element.addEventListener("click", this.method1.bind(this));
}

const ex = new Example();

// Деструктуризация
const { method1, method2 } = ex;

console.log(method1()); // TypeError — контекст потерян
console.log(method2()); // 42 — контекст сохранён (стрелка)

Производительность:

bind создаёт новую функцию-обёртку, что имеет небольшие накладные расходы:

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

Итог: bind — мощный инструмент для управления контекстом и частичного применения аргументов. Его ключевые особенности: создание новой функции вместо немедленного вызова, «жёсткая» привязка контекста (нельзя переопределить), игнорирование привязанного this при вызове через new. В современном коде для привязки контекста в классах предпочтительнее использовать стрелочные функции как свойства, но понимание bind необходимо для работы с legacy-кодом и продвинутых функциональных паттернов.

Вопрос 8. Как посчитать количество переданных аргументов в функцию, не меняя код внутри этой функции?

Таймкод: 00:54:32

Ответ собеседника: Неполный. Участники упомянули arguments (который работает только внутри функции) и идею обёртки. Ведущий подтвердил, что решение — функция-обёртка (декоратор) через apply. Однако никто не привёл полного рабочего примера кода с объяснением.

Правильный ответ:

Задача заключается в том, чтобы подсчитать количество аргументов, переданных при вызове функции, не модифицируя саму функцию. Это типичный сценарий для паттерна «декоратор» или «обёртка» (wrapper/decorator pattern).

Почему нельзя просто использовать arguments внутри функции:

Объект arguments доступен только внутри тела функции и содержит аргументы, переданные при текущем вызове. Если мы не можем менять код функции, arguments нам не поможет.

Решение — функция-обёртка (декоратор):

Идея: создать новую функцию, которая:

  1. Перехватывает все аргументы через arguments (или ...args)
  2. Подсчитывает их количество
  3. Вызывает оригинальную функцию с теми же аргументами
  4. Возвращает результат оригинальной функции

Реализация через apply (классический подход):

function countArgsWrapper(fn) {
return function() {
const argsCount = arguments.length;
console.log(`Функция ${fn.name || 'анонимная'} вызвана с ${argsCount} аргументами`);

// Вызываем оригинальную функцию с тем же контекстом и аргументами
return fn.apply(this, arguments);
};
}

// Пример использования
function sum(a, b, c) {
return (a || 0) + (b || 0) + (c || 0);
}

// Создаём обёртку
const countedSum = countArgsWrapper(sum);

console.log(countedSum(1, 2, 3)); // Вывод: "Функция sum вызвана с 3 аргументами", Результат: 6
console.log(countedSum(10, 20)); // Вывод: "Функция sum вызвана с 2 аргументами", Результат: 30
console.log(countedSum()); // Вывод: "Функция sum вызвана с 0 аргументами", Результат: 0

Реализация через spread-оператор (современный подход ES6+):

function countArgsWrapper(fn) {
return function(...args) {
console.log(`Функция ${fn.name || 'анонимная'} вызвана с ${args.length} аргументами`);
console.log(`Аргументы:`, args);

// Вызываем оригинальную функцию с тем же контекстом и аргументами
return fn.apply(this, args);
// Или эквивалентно: return fn.call(this, ...args);
};
}

// Пример
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}

const countedGreet = countArgsWrapper(greet);

const result = countedGreet("Привет", "Алексей", "!");
// Вывод: "Функция greet вызвана с 3 аргументами"
// Вывод: Аргументы: ["Привет", "Алексей", "!"]
console.log(result); // "Привет, Алексей!"

Продвинутая версия с накопительной статистикой:

function createCountedFunction(fn) {
let totalCalls = 0;
let totalArgs = 0;

const wrapper = function(...args) {
totalCalls++;
totalArgs += args.length;

console.log(`Вызов #${totalCalls}: ${args.length} аргументов`);
console.log(`Среднее количество аргументов: ${(totalArgs / totalCalls).toFixed(2)}`);

return fn.apply(this, args);
};

// Добавляем метод для получения статистики
wrapper.getStats = function() {
return {
totalCalls,
totalArgs,
averageArgs: totalCalls > 0 ? totalArgs / totalCalls : 0
};
};

// Метод для сброса статистики
wrapper.resetStats = function() {
totalCalls = 0;
totalArgs = 0;
};

return wrapper;
}

// Пример использования
function multiply(a, b) {
return a * b;
}

const countedMultiply = createCountedFunction(multiply);

countedMultiply(2, 3); // Вызов #1: 2 аргументов
countedMultiply(5, 10); // Вызов #2: 2 аргументов
countedMultiply(7, 8); // Вызов #3: 2 аргумента

console.log(countedMultiply.getStats());
// { totalCalls: 3, totalArgs: 6, averageArgs: 2 }

Пример с методами объектов (сохранение контекста):

const calculator = {
value: 0,

add(a, b) {
this.value += (a || 0) + (b || 0);
return this.value;
},

multiply(a, b) {
this.value *= (a || 1) * (b || 1);
return this.value;
}
};

// Оборачиваем все методы объекта с подсчётом аргументов
function wrapObjectMethods(obj) {
const wrapped = {};

for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'function') {
wrapped[key] = function(...args) {
console.log(`${key} вызван с ${args.length} аргументами`);
return obj[key].apply(obj, args); // Важно: передаём obj как this!
};
} else {
wrapped[key] = obj[key];
}
}

return wrapped;
}

const countedCalc = wrapObjectMethods(calculator);

countedCalc.add(5, 10); // "add вызван с 2 аргументами", значение: 15
countedCalc.multiply(2, 3); // "multiply вызван с 2 аргументами", значение: 90

Универсальный декоратор с поддержкой различных метрик:

function createInstrumentedFunction(fn, options = {}) {
const {
onCall = null, // коллбэк при вызове
logArgs = false, // логировать ли аргументы
logResult = false, // логировать ли результат
measureTime = false // измерять ли время выполнения
} = options;

const stats = {
calls: 0,
totalArgs: 0,
totalTime: 0
};

const wrapper = function(...args) {
const callNumber = ++stats.calls;
stats.totalArgs += args.length;

if (logArgs) {
console.log(`[${fn.name}] Вызов #${callNumber}, аргументы:`, args);
} else {
console.log(`[${fn.name}] Вызов #${callNumber}, аргументов: ${args.length}`);
}

let startTime;
if (measureTime) startTime = performance.now();

const result = fn.apply(this, args);

if (measureTime) {
const duration = performance.now() - startTime;
stats.totalTime += duration;
console.log(`[${fn.name}] Время выполнения: ${duration.toFixed(3)}мс`);
}

if (logResult) {
console.log(`[${fn.name}] Результат:`, result);
}

if (onCall) {
onCall({ callNumber, args, result, stats });
}

return result;
};

wrapper.getStats = () => ({
...stats,
averageArgs: stats.calls > 0 ? stats.totalArgs / stats.calls : 0,
averageTime: stats.calls > 0 ? stats.totalTime / stats.calls : 0
});

return wrapper;
}

// Пример использования
function fetchData(url, options) {
// Имитация работы
return `Данные с ${url}`;
}

const instrumentedFetch = createInstrumentedFunction(fetchData, {
logArgs: true,
logResult: true,
measureTime: true
});

instrumentedFetch("/api/users", { method: "GET" });
instrumentedFetch("/api/posts", { method: "POST" });

console.log(instrumentedFetch.getStats());
// { calls: 2, totalArgs: 4, totalTime: ..., averageArgs: 2, averageTime: ... }

Ключевые моменты решения:

  1. fn.apply(this, arguments) или fn.apply(this, args) — передача контекста и всех аргументов оригинальной функции. Это критически важно для корректной работы методов объектов.

  2. Возврат результата — обёртка должна возвращать то же значение, что и оригинальная функция, чтобы быть прозрачной для вызывающего кода.

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

  4. Стрелочные функции не подходят для обёртки, если нужно сохранить this вызова — у стрелок нет собственного this и arguments. Нужно использовать обычные function.

// НЕПРАВИЛЬНО — стрелка не имеет arguments и не сохраняет this вызова
const badWrapper = fn => (...args) => {
console.log(args.length);
return fn(...args); // this потерян!
};

// ПРАВИЛЬНО — обычная функция сохраняет this через apply
const goodWrapper = fn => function(...args) {
console.log(args.length);
return fn.apply(this, args); // this сохранён!
};

Итог: Паттерн обёртки (wrapper/decorator) — стандартное решение для добавления функциональности к существующим функциям без их модификации. Ключевые элементы: перехват аргументов через arguments или ...args, подсчёт их количества, вызов оригинала через apply с сохранением контекста this и возврат результата. Этот паттерн широко используется в логировании, профилировании, мемоизации, валидации и других сценариях метапрограммирования.

Вопрос 9. Каковы отличия стрелочных функций от обычных (классических) функций в JavaScript?

Таймкод: 01:01:20

Ответ собеседника: Неполный. Участники назвали ключевые отличия: отсутствие собственного this (лексическое связывание), отсутствие arguments, невозможность использования с new. Ведущий дополнил полный список из 11 отличий. Участники не смогли назвать все отличия самостоятельно.

Правильный ответ:

Стрелочные функции (arrow functions), появившиеся в ES6, имеют ряд фундаментальных отличий от обычных функций. Эти отличия касаются не только синтаксиса, но и семантики выполнения.

Полный список отличий:

1. Лексическое связывание this (нет собственного this)

Это самое важное отличие. Обычные функции получают this в момент вызота (динамический контекст). Стрелочные функции захватывают this из окружающей лексической области видимости при создании (статический контекст).

// Обычная функция — this определяется при вызове
const obj1 = {
name: "Объект 1",
regularMethod() {
console.log(this.name); // this — объект вызова

setTimeout(function() {
console.log(this.name); // this — window/undefined (потеря контекста)
}, 100);
}
};

// Стрелочная функция — this захватывается при создании
const obj2 = {
name: "Объект 2",
arrowMethod() {
console.log(this.name); // this — obj2

setTimeout(() => {
console.log(this.name); // this — obj2 (сохранён лексически)
}, 100);
}
};

obj1.regularMethod(); // "Объект 1", undefined (или window.name)
obj2.arrowMethod(); // "Объект 2", "Объект 2"

2. Отсутствие объекта arguments

Стрелочные функции не имеют собственного объекта arguments. Для получения аргументов используйте rest-параметр ...args.

// Обычная функция
function regularFn() {
console.log(arguments); // Arguments(3) [1, 2, 3]
}
regularFn(1, 2, 3);

// Стрелочная функция
const arrowFn = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};
// arrowFn(1, 2, 3); // Ошибка!

// Решение — rest-параметр
const arrowFnFixed = (...args) => {
console.log(args); // [1, 2, 3]
};
arrowFnFixed(1, 2, 3);

3. Невозможность использования как конструктор (нет [[Construct]])

Стрелочные функции нельзя вызывать с new — будет TypeError.

function RegularClass(name) {
this.name = name;
}
const instance1 = new RegularClass("Тест"); // Работает

const ArrowClass = (name) => {
this.name = name;
};
// const instance2 = new ArrowClass("Тест"); // TypeError: ArrowClass is not a constructor

4. Отсутствие свойства prototype

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

function regularFn() {}
console.log(regularFn.prototype); // {constructor: ƒ}

const arrowFn = () => {};
console.log(arrowFn.prototype); // undefined

5. call, apply, bind не могут изменить this

Поскольку this стрелочной функции определяется лексически, методы call, apply и bind не могут его переопределить.

const obj = { name: "Целевой объект" };

const arrowFn = () => {
console.log(this.name);
};

// Ни один из этих вызовов не изменит this!
arrowFn.call(obj); // this остаётся прежним
arrowFn.apply(obj); // this остаётся прежним
arrowFn.bind(obj)(); // this остаётся прежним

6. Нет свойства new.target

new.target позволяет определить, была ли функция вызвана через new. В стрелочных функциях это свойство недоступно.

function regularFn() {
if (new.target) {
console.log("Вызвана через new");
} else {
console.log("Обычный вызов");
}
}

const arrowFn = () => {
// console.log(new.target); // SyntaxError в некоторых контекстах
};

regularFn(); // "Обычный вызов"
new regularFn(); // "Вызвана через new"

7. Невозможность использования yield (не могут быть генераторами)

Стрелочные функции нельзя объявить как генераторы (с function*).

// Обычный генератор
function* regularGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = regularGenerator();
console.log(gen.next().value); // 1

// Стрелочный генератор — синтаксическая ошибка!
// const arrowGenerator = *() => { // SyntaxError
// yield 1;
// };

8. Неявный return (implicit return)

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

// Обычная функция — нужен явный return
const regularAdd = function(a, b) {
return a + b;
};

// Стрелочная функция — неявный return
const arrowAdd = (a, b) => a + b;

// Возврат объекта без неявного return — нужны скобки
const createUser = (name) => ({ name: name, active: true });
// Без скобок {} интерпретируется как блок кода, а не объект

console.log(arrowAdd(2, 3)); // 5
console.log(createUser("Иван")); // { name: "Иван", active: true }

9. Нет hoisting (поднятия)

Стрелочные функции, объявленные через const или let, не поднимаются и находятся в TDZ до объявления.

// Function Declaration — поднимается
hoistedRegular(); // Работает!
function hoistedRegular() {
console.log("Я поднята!");
}

// Стрелочная функция через const — не поднимается
// notHoistedArrow(); // ReferenceError: Cannot access 'notHoistedArrow' before initialization
const notHoistedArrow = () => {
console.log("Я не поднята!");
};
notHoistedArrow(); // Работает только после объявления

10. Невозможность использования повторяющихся именованных параметров

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

// Нестрогий режим — обычная функция допускает дублирование
function regularFn(a, a) {
return a; // Вернёт второй аргумент
}
console.log(regularFn(1, 2)); // 2

// Стрелочная функция — SyntaxError
// const arrowFn = (a, a) => a; // SyntaxError: Duplicate parameter name not allowed

11. Методы-стрелки в классах не находятся в прототипе

Когда стрелочная функция используется как метод класса, она становится свойством экземпляра, а не методом прототипа. Это влияет на наследование и super.

class Parent {
parentMethod() {
return "Метод родителя";
}
}

class Child extends Parent {
// Обычный метод — в прототипе, super работает
regularMethod() {
return super.parentMethod() + " + потомок";
}

// Стрелочная метод — свойство экземпляра, super не работает
// arrowMethod = () => {
// return super.parentMethod(); // SyntaxError: 'super' keyword unexpected here
// };
}

const child = new Child();
console.log(child.regularMethod()); // "Метод родителя + потомок"

Сводная таблица отличий:

ХарактеристикаОбычная функцияСтрелочная функция
thisДинамический (определяется при вызове)Лексический (захватывается при создании)
argumentsЕстьНет (используйте ...args)
new (конструктор)Можно использоватьНельзя использовать
prototypeЕстьНет
call/apply/bindМеняют thisНе меняют this
new.targetДоступенНедоступен
Генератор (yield)Может быть генераторомНе может быть генератором
Неявный returnНет (нужен return)Да (без фигурных скобок)
HoistingFunction Declaration поднимаетсяНе поднимается (как const/let)
Повторяемые параметрыДопускаются (нестрогий режим)Не допускаются
Метод в классеВ прототипе, super работаетСвойство экземпляра, super не работает

Когда использовать стрелочные функции:

  • Коллбэки и обработчики событий, где нужно сохранить this
  • Функции высшего порядка (map, filter, reduce)
  • Короткие однострочные функции
  • Методы классов, которые будут передаваться как коллбэки
// Хорошие случаи для стрелок
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

// Обработчик с сохранением this
class Timer {
constructor() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // this сохранён!
}, 1000);
}
}

Когда НЕ использовать стрелочные функции:

  • Методы объектов, которые должны иметь свой this
  • Конструкторы (невозможно)
  • Генераторы (невозможно)
  • Методы прототипа (теряется преимущество прототипного наследования)
  • Когда нужен arguments
// Плохие случаи для стрелок
const badObject = {
name: "Тест",
// Стрелка — this будет не объект!
getName: () => this.name // this — внешний контекст, не badObject
};

// Метод прототипа — стрелка не попадёт в прототип
function BadClass() {
this.value = 42;
}
BadClass.prototype.getValue = () => this.value; // this — не экземпляр!

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