call и apply
callиapply— методыFunction.prototype, позволяющие вызвать функцию с явно указанным контекстом (this) и аргументами. Отличаются способом передачи аргументов.
Зачем нужно
call и apply дают полный контроль над значением this при вызове функции. Это нужно для заимствования методов, реализации полиморфизма, работы с функциями-обёртками и делегирования вызовов.
Где используется
Заимствование методов (method borrowing), вызов функции с динамическим контекстом, передача аргументов из массива, реализация наследования (до ES6), декораторы, polyfills.
Предпосылки
Синтаксис
// call — аргументы через запятую
fn.call(thisArg, arg1, arg2, ...);
// apply — аргументы массивом
fn.apply(thisArg, [arg1, arg2, ...]);
call — вызов с контекстом
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user1 = { name: 'Иван' };
const user2 = { name: 'Мария' };
console.log(greet.call(user1, 'Привет', '!'));
// "Привет, Иван!"
console.log(greet.call(user2, 'Здравствуйте', '.'));
// "Здравствуйте, Мария."
apply — вызов с массивом аргументов
function introduce(greeting, role) {
return `${greeting}, я ${this.name}, ${role}`;
}
const dev = { name: 'Алиса' };
const args = ['Привет', 'разработчик'];
console.log(introduce.apply(dev, args));
// "Привет, я Алиса, разработчик"
Разница между call и apply
// call — аргументы списком
Math.max.call(null, 1, 2, 3); // 3
// apply — аргументы массивом
Math.max.apply(null, [1, 2, 3]); // 3
// Мнемоника:
// Call — Comma (запятые)
// Apply — Array (массив)
// В современном JS spread заменяет apply:
Math.max(...[1, 2, 3]); // 3
Заимствование методов (Method Borrowing)
Array-методы для array-like объектов
// arguments — не массив, но похож на массив
function example() {
// Заимствуем Array.prototype.slice
const args = Array.prototype.slice.call(arguments);
console.log(args); // настоящий массив
// Заимствуем forEach
Array.prototype.forEach.call(arguments, (arg) => {
console.log(arg);
});
// Современная альтернатива:
const argsModern = Array.from(arguments);
const argsSpread = [...arguments];
}
example(1, 2, 3);
Заимствование toString
// Определение реального типа объекта
function getType(value) {
return Object.prototype.toString().call(value);
}
console.log(getType()); // "[object Array]"
console.log(getType({})); // "[object Object]"
console.log(getType(null)); // "[object Null]"
console.log(getType(undefined)); // "[object Undefined]"
console.log(getType(42)); // "[object Number]"
console.log(getType(/regex/)); // "[object RegExp]"
Заимствование метода у другого объекта
const dog = {
name: 'Бобик',
speak {
return `${this.name} лает: Гав!`;
}
};
const cat = {
name: 'Мурка'
};
// Кот использует метод собаки
console.log(dog.speak.call(cat));
// "Мурка лает: Гав!" — работает, но абсурдно :)
Практические примеры
Наследование конструкторов (до ES6)
function Animal(name, sound) {
this.name = name;
this.sound = sound;
}
function Dog(name) {
// Вызываем конструктор родителя с нашим this
Animal.call(this, name, 'Гав');
this.tricks = ;
}
const dog = new Dog('Рекс');
console.log(dog.name); // 'Рекс'
console.log(dog.sound); // 'Гав'
Применение apply для spread аргументов
// До spread-оператора
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers); // 7
const min = Math.min.apply(null, numbers); // 2
// Современный эквивалент
const maxModern = Math.max(...numbers);
Вызов с null/undefined
// Когда контекст не важен — передаём null
function sum(a, b) {
return a + b;
}
sum.call(null, 1, 2); // 3
sum.apply(null, [1, 2]); // 3
// Осторожно: в non-strict mode null/undefined заменяется на globalThis
// В strict mode this остаётся null/undefined
Динамический вызов метода
const operations = {
add(a, b) { return a + b; },
subtract(a, b) { return a - b; },
multiply(a, b) { return a * b; },
};
function execute(operation, ...args) {
if (operations[operation]) {
return operations[operation].apply(null, args);
}
throw new Error(`Неизвестная операция: ${operation}`);
}
console.log(execute('add', 5, 3)); // 8
console.log(execute('multiply', 4, 7)); // 28
Декоратор с сохранением контекста
function withLogging(fn) {
return function(...args) {
console.log(`Вызов ${fn.name} с`, args);
// Сохраняем this через apply
const result = fn.apply(this, args);
console.log(`Результат:`, result);
return result;
};
}
const user = {
name: 'Иван',
greet: withLogging(function greet(greeting) {
return `${greeting}, ${this.name}`;
})
};
user.greet('Привет');
// Вызов greet с ['Привет']
// Результат: Привет, Иван
Reflect.apply (ES6)
// Современная альтернатива Function.prototype.apply
function sum(a, b) {
return a + b;
}
const result = Reflect.apply(sum, null, [1, 2]);
console.log(result); // 3
// Полезно когда apply может быть переопределён
Частые ошибки
1. Путаница call и apply
function test(a, b, c) {
console.log(a, b, c);
}
// Неправильно — передали массив как ОДИН аргумент в call
test.call(null, [1, 2, 3]); // [1, 2, 3] undefined undefined
// Правильно
test.call(null, 1, 2, 3); // 1 2 3
test.apply(null, [1, 2, 3]); // 1 2 3
2. Забытый контекст в декораторе
// Плохо — потеря this
function bad(fn) {
return function(...args) {
return fn(...args); // this не передаётся!
};
}
// Хорошо — apply сохраняет this
function good(fn) {
return function(...args) {
return fn.apply(this, args);
};
}
3. Вызов с примитивом как this
function test() {
console.log(typeof this, this);
}
// В non-strict mode: примитив оборачивается в объект
test.call(42); // "object" Number {42}
test.call('hello'); // "object" String {'hello'}
// В strict mode: остаётся примитивом
// "number" 42
Практика
- Напиши функцию
greetи вызови её с разными контекстами через call - Используй apply для передачи массива аргументов в Math.max
- Заимствуй метод
Array.prototype.joinдля arguments-объекта - Реализуй функцию
invoke(obj, methodName, ...args)через call - Напиши декоратор, который сохраняет контекст через apply
Связанные темы
Ресурсы
- MDN — Function.prototype.call
- MDN — Function.prototype.apply
- JavaScript.info — Декораторы и call/apply
🎓 Источник: Функции, стрелочные функции, контексты
- 📅 2018-09-27 · YouTube · ID:
pn5myCmpV2U - Тезисы:
f1.call(null, 2, 3)— аргументы через запятую, первый —thisf1.apply(null, [2, 3])— массив аргументов, первый —this- Spread-оператор
f1(...arr)— эквивалентf1.apply(null, arr) - Spread на вызове разбивает массив на аргументы — современная альтернатива
apply
- Цитата: «Я могу сюда передать какой-то объект, и этот объект будет виден в функции F1 через идентификатор this.»
⚡ Источник: Как работает this · AsForJS
- 📅 2023-05-07 · YouTube · ID:
4tg4qokVS9o - Тезисы:
call/apply/bind— высший приоритет для заданияthisthisчерез call/apply может быть любым значением, даже примитивом:fn.call(1)→this === 1(strict)- В non-strict примитив проходит ToObject и оборачивается
- На стрелочных функциях
call/applyдляthisНЕ работают