Mock Interview c подписчками на Junior Frontend позицию
Сегодня мы разберем запись прямого эфира, посвященного проведению собеседования на позицию 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
Ключевые отличия:
-
Стандартизация: Нативные объекты определены в спецификации ECMAScript и ведут себя одинаково во всех средах. Host-объекты могут различаться между браузерами или отсутствовать в других средах (например,
documentне существует в Node.js). -
Доступность: Нативные объекты всегда доступны. Host-объекты зависят от среды и могут быть недоступны.
-
Поведение: Нативные объекты следуют строгим правилам спецификации. Host-объекты могут иметь специфичное поведение, не описанное в стандарте (например, host-объекты могут нарушать некоторые правила, такие как запрет на удаление свойств).
-
Наследование: Нативные объекты имеют предсказуемую цепочку прототипов. 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,Uint8ClampedArrayInt16Array,Uint16ArrayInt32Array,Uint32ArrayFloat32Array,Float64ArrayBigInt64Array,BigUint64Array
Утилиты:
Math— математические константы и функцииDate— работа с датой и временемRegExp— регулярные выраженияJSON— сериализация и десериализация JSONReflect— метаданные и рефлексия (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; };
Ключевые правила для решения таких задач:
-
Определите, как объявлена переменная:
- Не объявлена нигде →
typeofвернёт"undefined", прямое обращение →ReferenceError var→ hoisting сundefined, обращение до объявления →undefinedlet/const→ hoisting без инициализации, обращение до объявления →ReferenceError(включаяtypeof!)
- Не объявлена нигде →
-
Помните про исключения
typeof:typeof null === "object"(баг)typeof function(){} === "function"(хотя функции — объекты)
-
Код выполняется последовательно: если на какой-то строке возникает непойманная ошибка, выполнение прерывается, и последующие строки не выполняются.
-
Function Declaration vs Function Expression:
function foo(){}— полностью поднимается, можно вызывать до объявленияvar foo = function(){}— поднимается какundefined, вызов до объявления →TypeErrorlet 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"
Разбор по шагам:
typeof 3→"number"(унарный оператор, высокий приоритет)typeof "hello"→"string""number" + " "→"number "(конкатенация строк)"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, !, ~, +(унарный), -(унарный) | Унарные операторы |
| ... | *, /, % | Мультипликативные |
| ... | +, - | Аддитивные (включая конкатенацию) |
| ... | , | Оператор запятой (самый низкий) |
Практический алгоритм решения таких задач:
- Найдите скобки — они задают порядок вычисления
- Внутри скобок с запятыми — запомните, что возвращается последнее значение
- Определите typeof — он применяется к результату выражения справа
- Оператор + — если хотя бы один операнд строка, происходит конкатенация
- Выполняйте слева направо — при равном приоритете
Пример полного разбора:
// Допустим, код выглядит так:
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
Ключевые правила для запоминания:
typeofимеет высокий приоритет и привязывается к ближайшему операнду справа- Оператор запятой
,возвращает последнее значение из списка - Оператор
+с хотя бы одной строкой выполняет конкатенацию - Скобки
()вокруг выражения с запятыми заставляют вычислить всё выражение целиком перед применением внешнего оператора 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 нам не поможет.
Решение — функция-обёртка (декоратор):
Идея: создать новую функцию, которая:
- Перехватывает все аргументы через
arguments(или...args) - Подсчитывает их количество
- Вызывает оригинальную функцию с теми же аргументами
- Возвращает результат оригинальной функции
Реализация через 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: ... }
Ключевые моменты решения:
-
fn.apply(this, arguments)илиfn.apply(this, args)— передача контекста и всех аргументов оригинальной функции. Это критически важно для корректной работы методов объектов. -
Возврат результата — обёртка должна возвращать то же значение, что и оригинальная функция, чтобы быть прозрачной для вызывающего кода.
-
Прозрачность — вызывающий код не должен замечать разницы между оригинальной функцией и обёрткой (кроме побочного эффекта в виде логирования).
-
Стрелочные функции не подходят для обёртки, если нужно сохранить
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) | Да (без фигурных скобок) |
| Hoisting | Function 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 и рядом ограничений. Понимание всех отличий критически важно для выбора правильного типа функции в каждом конкретном случае и для отладки проблем с контекстом выполнения.
