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).