this — первый аргумент .call() или .apply() при непрямом вызове.
Правило простое, но полезность спрятана глубже: .call() и .apply() дают возможность «одолжить» чужой метод и вызвать его в собственном контексте — частая идиома при работе со стандартной библиотекой JavaScript.
Синтаксис и разница
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Аня' };
// .call() — аргументы через запятую
greet.call(person, 'Привет', '!'); // 'Привет, Аня!'
// .apply() — аргументы массивом
greet.apply(person, ['Привет', '!']); // 'Привет, Аня!'
Правило запоминания: call — comma-separated, apply — array. Функционально они эквивалентны. Разница только в аргументах.
С появлением spread-оператора (..., ES6) необходимость в .apply() заметно снизилась — массив аргументов можно раскрыть в call-нотацию:
const args = ['Привет', '!'];
// старая практика — apply
greet.apply(person, args);
// современная — spread + call
greet.call(person, ...args);
В новом коде .call() со spread встречается чаще, чем .apply(). Последний остаётся в старых кодовых базах.
Use case 1: заимствование методов
Классический сценарий — «одолжить» метод одного объекта для вызова в контексте другого. Например, у массивов есть удобный метод .slice(), а у псевдо-массивов вроде NodeList его нет. Можно вызвать Array.prototype.slice с NodeList как this:
const nodes = document.querySelectorAll('.item');
// nodes — NodeList, не Array; .slice() у него нет
const first3 = Array.prototype.slice.call(nodes, 0, 3);
// теперь first3 — обычный массив с первыми тремя элементами
В современном коде для этого чаще используют Array.from(nodes) или spread [...nodes], но паттерн через .call() с prototype всё ещё встречается в библиотеках и полифилах.
Use case 2: safe hasOwnProperty
Классическая идиома — проверить, что свойство принадлежит именно объекту, а не унаследовано от прототипа. Наивная запись obj.hasOwnProperty(key) может сломаться, если объект переопределил свой hasOwnProperty или создан через Object.create(null) (нет прототипа вообще).
const config = Object.create(null);
config.theme = 'dark';
// упадёт — нет hasOwnProperty в прототипе
config.hasOwnProperty('theme');
// TypeError: config.hasOwnProperty is not a function
// правильный способ через .call()
Object.prototype.hasOwnProperty.call(config, 'theme');
// => true
С 2022 года есть более короткая версия — Object.hasOwn(obj, key) (ES2022). Она делает то же самое без обходного пути через .call(). Но паттерн Object.prototype.method.call(obj, ...) остаётся полезным для других методов, где безопасный вызов важен.
Use case 3: работа с arguments в старых функциях
Обычные функции имеют псевдо-массив arguments. Он выглядит как массив, но у него нет .map, .filter и других методов. Раньше приходилось использовать .call() для конверсии:
function oldFormatAll() {
// arguments — псевдо-массив
const args = Array.prototype.slice.call(arguments);
return args.map(x => String(x).toUpperCase()).join(' ');
}
oldFormatAll('hello', 'world'); // 'HELLO WORLD'
В современном коде это делается через rest-параметр (...args), который сразу даёт настоящий массив:
function newFormatAll(...args) {
return args.map(x => String(x).toUpperCase()).join(' ');
}
newFormatAll('hello', 'world'); // 'HELLO WORLD'
Что не работает: стрелочные функции
Один нюанс: .call() и .apply() не работают на стрелочных функциях. У стрелочной нет своего this — она берёт его лексически. Попытка переопределить контекст через .call() просто игнорируется:
const greet = () => this.name;
greet.call({ name: 'Аня' }); // не сработает — this внутри — глобальный
Подробнее в главе 7.
Связь с bind()
Разница между .call() и .bind():
- .call(x, ...args) — вызывает функцию прямо сейчас с this = x.
- .bind(x) — возвращает новую функцию, у которой this = x навсегда. Функцию можно позвать позже, много раз.
Если нужно один разовый вызов — .call(). Если нужна связанная-навсегда функция для передачи как callback — .bind() (см. главу 6).