Кратко
СкопированоДля изменения контекста выполнения функции используется один из трёх методов (ведь функция в JavaScript является объектом и, как любой объект, может иметь собственные методы):
bind— возвращает новую функцию с привязанным( ) this;call— выполняет функцию, передавая ей( ) thisи аргументы;apply— выполняет функцию, передавая ей( ) thisи массив аргументов.
Bind
СкопированоСоздаёт новую функцию с указанным this и (опционально) аргументами.
Как пишется
Скопировано
const newFunc = func.bind(context, arg1, arg2, ...)
const newFunc = func.bind(context, arg1, arg2, ...)
Как понять
СкопированоВозвращает новую функцию. Первый аргумент становится this внутри функции — и его нельзя изменить повторным вызовом bind.
const cat = { name: 'Барсик' }const dog = { name: 'Шарик' }function sayHi() { console.log(`Привет, я ${this.name}`)}// Привязываем к котуconst bound = sayHi.bind(cat)bound()// "Привет, я Барсик"// Пытаемся переопределить контекстbound.call(dog)// "Привет, я Барсик" — не изменилось!bound.apply(dog)// "Привет, я Барсик" — не изменилось!const boundAgain = bound.bind(dog);boundAgain()// "Привет, я Барсик" — даже двойной bind не помог!
const cat = { name: 'Барсик' }
const dog = { name: 'Шарик' }
function sayHi() {
console.log(`Привет, я ${this.name}`)
}
// Привязываем к коту
const bound = sayHi.bind(cat)
bound()
// "Привет, я Барсик"
// Пытаемся переопределить контекст
bound.call(dog)
// "Привет, я Барсик" — не изменилось!
bound.apply(dog)
// "Привет, я Барсик" — не изменилось!
const boundAgain = bound.bind(dog);
boundAgain()
// "Привет, я Барсик" — даже двойной bind не помог!
Аргументы после первого подставляются в функцию один за другим. Можно указать не все, а только первые — остальные добавить уже при вызове получившейся функции. Пример:
const customer = { name: 'Анна' };function orderPizza(size, topping) { console.log(`${this.name} заказала ${size} пиццу с ${topping}`);}// Фиксируем size = 'большую'const annasOrder = orderPizza.bind(customer, 'большую');annasOrder('грибами');// "Анна заказала большую пиццу с грибами"annasOrder('пепперони');// "Анна заказала большую пиццу с пепперони"
const customer = { name: 'Анна' };
function orderPizza(size, topping) {
console.log(`${this.name} заказала ${size} пиццу с ${topping}`);
}
// Фиксируем size = 'большую'
const annasOrder = orderPizza.bind(customer, 'большую');
annasOrder('грибами');
// "Анна заказала большую пиццу с грибами"
annasOrder('пепперони');
// "Анна заказала большую пиццу с пепперони"
Когда использовать
Скопировано- при передаче функции в setTimeout/setInterval/addEventListener, чтобы она не потеряла контекст выполнения;
- фиксация
thisв функции; - создание функции с предустановленными аргументами, каррирование.
Пример:
const bakery = { name: 'Хлебница', order(clientName, product) { console.log(`${clientName} заказал(а) ${product} в пекарне «${this.name}»`) }}// 1. Передача в setTimeout (сохранение контекста)setTimeout(bakery.order.bind(bakery, 'Анна', 'багет'), 1000)// через секунду: "Анна заказал(а) багет в пекарне «Хлебница»"// 2. Фиксация thisconst placeOrder = bakery.order.bind(bakery)placeOrder('Пётр', 'круассан')// "Пётр заказал(а) круассан в пекарне «Хлебница»"// 3. Каррирование (фиксация клиента пекарни)const annaOrder = bakery.order.bind(bakery, 'Анна')annaOrder('эклер')// "Анна заказал(а) эклер в пекарне «Хлебница»"annaOrder('пирожок')// "Анна заказал(а) пирожок в пекарне «Хлебница»"
const bakery = {
name: 'Хлебница',
order(clientName, product) {
console.log(`${clientName} заказал(а) ${product} в пекарне «${this.name}»`)
}
}
// 1. Передача в setTimeout (сохранение контекста)
setTimeout(bakery.order.bind(bakery, 'Анна', 'багет'), 1000)
// через секунду: "Анна заказал(а) багет в пекарне «Хлебница»"
// 2. Фиксация this
const placeOrder = bakery.order.bind(bakery)
placeOrder('Пётр', 'круассан')
// "Пётр заказал(а) круассан в пекарне «Хлебница»"
// 3. Каррирование (фиксация клиента пекарни)
const annaOrder = bakery.order.bind(bakery, 'Анна')
annaOrder('эклер')
// "Анна заказал(а) эклер в пекарне «Хлебница»"
annaOrder('пирожок')
// "Анна заказал(а) пирожок в пекарне «Хлебница»"
Call
СкопированоВызывает функцию с указанным this и аргументами по порядку.
Как пишется
Скопировано
func.call(context, arg1, arg2, ...)
func.call(context, arg1, arg2, ...)
Как понять
СкопированоМетод выполняет функцию. Первый аргумент становится this внутри функции, а все остальные аргументы передаются в функцию по порядку.
function addOrder(ingredient1, ingredient2) { console.log(`${this.customer} заказал(а) пиццу с ${ingredient1} и ${ingredient2}`);}const order1 = { customer: "Мария" };const order2 = { customer: "Пётр" };addOrder.call(order1, "сыром", "грибами");// Мария заказал(а) пиццу с сыром и грибамиaddOrder.call(order2, "ветчиной", "ананасами");// Пётр заказал(а) пиццу с ветчиной и ананасами
function addOrder(ingredient1, ingredient2) {
console.log(`${this.customer} заказал(а) пиццу с ${ingredient1} и ${ingredient2}`);
}
const order1 = { customer: "Мария" };
const order2 = { customer: "Пётр" };
addOrder.call(order1, "сыром", "грибами");
// Мария заказал(а) пиццу с сыром и грибами
addOrder.call(order2, "ветчиной", "ананасами");
// Пётр заказал(а) пиццу с ветчиной и ананасами
Когда использовать
СкопированоДля вызова функции с конкретным this, если аргументы находятся не в массиве.
Apply
СкопированоВызывает функцию с указанным this и аргументами в виде массива.
Как пишется
Скопировано
func.apply(context, [arg1, arg2, ...])
func.apply(context, [arg1, arg2, ...])
Как понять
СкопированоМетод выполняет функцию. Первый аргумент становится this внутри функции, а второй (массив) — её аргументами.
function makePizza(...toppings) { console.log(`${this.chef} готовит пиццу с: ${toppings.join(", ")}`);}const pizzeria1 = { chef: "Алессандро" };const pizzeria2 = { chef: "Фабрицио" };const toppings1 = ["пепперони", "сыром", "базиликом"];const toppings2 = ["курицей", "ананасами"]makePizza.apply(pizzeria1, toppings1);// Алессандро готовит пиццу с: пепперони, сыром, базиликомmakePizza.apply(pizzeria2, toppings2);// Фабрицио готовит пиццу с: курицей, ананасами
function makePizza(...toppings) {
console.log(`${this.chef} готовит пиццу с: ${toppings.join(", ")}`);
}
const pizzeria1 = { chef: "Алессандро" };
const pizzeria2 = { chef: "Фабрицио" };
const toppings1 = ["пепперони", "сыром", "базиликом"];
const toppings2 = ["курицей", "ананасами"]
makePizza.apply(pizzeria1, toppings1);
// Алессандро готовит пиццу с: пепперони, сыром, базиликом
makePizza.apply(pizzeria2, toppings2);
// Фабрицио готовит пиццу с: курицей, ананасами
Когда использовать
СкопированоДля вызова функции с конкретным this, если аргументы уже в массиве.
До появления spread-оператора apply был единственным удобным способом найти максимум/минимум в массиве чисел, т. к. в Math и Math нельзя передать массив:
const prices = [12, 18, 14, 22, 16];// Математическая функция ожидает отдельные числа, а не массивconsole.log(Math.max(prices))// NaN — не работает!// Математическая функция не использует this, поэтому можно передать nullconsole.log(Math.max.apply(null, prices))// 22 — всё работает!// Современный способ — spread-оператор:console.log(Math.max(...prices))
const prices = [12, 18, 14, 22, 16];
// Математическая функция ожидает отдельные числа, а не массив
console.log(Math.max(prices))
// NaN — не работает!
// Математическая функция не использует this, поэтому можно передать null
console.log(Math.max.apply(null, prices))
// 22 — всё работает!
// Современный способ — spread-оператор:
console.log(Math.max(...prices))
Сравнение методов
СкопированоКаждый из методов по-своему решает задачу привязки this функции:
| Метод | Выполняет функцию? | Возвращает | Передача аргументов |
|---|---|---|---|
call | да | результат функции | по одному, через запятую |
apply | да | результат функции | массивом |
bind | нет | новую функцию | по одному (можно частями) |
Пример со всеми тремя методами:
const customer = { name: 'Анна' }function orderPizza(size, topping) { console.log(`${this.name} заказала ${size} пиццу с ${topping}`)}// call — сразу, аргументы через запятуюorderPizza.call(customer, 'большую', 'грибами')// "Анна заказала большую пиццу с грибами"// apply — сразу, аргументы массивомorderPizza.apply(customer, ['среднюю', 'пепперони'])// "Анна заказала среднюю пиццу с пепперони"// bind — возвращает новую функциюconst annasOrder = orderPizza.bind(customer, 'маленькую')annasOrder('сыром и помидорами')// "Анна заказала маленькую пиццу с сыром и помидорами"annasOrder('ветчиной')// "Анна заказала маленькую пиццу с ветчиной"
const customer = { name: 'Анна' }
function orderPizza(size, topping) {
console.log(`${this.name} заказала ${size} пиццу с ${topping}`)
}
// call — сразу, аргументы через запятую
orderPizza.call(customer, 'большую', 'грибами')
// "Анна заказала большую пиццу с грибами"
// apply — сразу, аргументы массивом
orderPizza.apply(customer, ['среднюю', 'пепперони'])
// "Анна заказала среднюю пиццу с пепперони"
// bind — возвращает новую функцию
const annasOrder = orderPizza.bind(customer, 'маленькую')
annasOrder('сыром и помидорами')
// "Анна заказала маленькую пиццу с сыром и помидорами"
annasOrder('ветчиной')
// "Анна заказала маленькую пиццу с ветчиной"
Стрелочные функции
СкопированоВсе три метода не работают со стрелочными функциями для изменения this. Стрелочные функции полностью игнорируют первый аргумент, переданный в bind, call или apply, но применяют остальные переданные аргументы:
const user = { name: 'Анна' }// Обычная функцияfunction regular(greeting) { console.log(`${greeting}, я ${this.name}`)}// Стрелочная функцияconst arrow = (greeting) => { console.log(`${greeting}, я ${this.name}`)}regular.call(user, 'Привет')// "Привет, я Анна"arrow.call(user, 'Привет')// "Привет, я undefined"regular.apply(user, ['Здравствуйте'])// "Здравствуйте, я Анна"arrow.apply(user, ['Здравствуйте'])// "Здравствуйте, я undefined"const boundRegular = regular.bind(user, 'Hi')boundRegular()// "Hi, я Анна"const boundArrow = arrow.bind(user, 'Hi')boundArrow()// "Hi, я undefined"
const user = { name: 'Анна' }
// Обычная функция
function regular(greeting) {
console.log(`${greeting}, я ${this.name}`)
}
// Стрелочная функция
const arrow = (greeting) => {
console.log(`${greeting}, я ${this.name}`)
}
regular.call(user, 'Привет')
// "Привет, я Анна"
arrow.call(user, 'Привет')
// "Привет, я undefined"
regular.apply(user, ['Здравствуйте'])
// "Здравствуйте, я Анна"
arrow.apply(user, ['Здравствуйте'])
// "Здравствуйте, я undefined"
const boundRegular = regular.bind(user, 'Hi')
boundRegular()
// "Hi, я Анна"
const boundArrow = arrow.bind(user, 'Hi')
boundArrow()
// "Hi, я undefined"
При этом bind можно использовать для каррирования стрелочной функции:
const log = (prefix, message) => { console.log(`[${prefix}] ${message}`)}// Фиксируем prefix = 'ERROR'const error = log.bind(null, 'ERROR')// Фиксируем prefix = 'INFO'const info = log.bind(null, 'INFO')error('Что-то пошло не так')// "[ERROR] Что-то пошло не так"info('Сервер запущен')// "[INFO] Сервер запущен"
const log = (prefix, message) => {
console.log(`[${prefix}] ${message}`)
}
// Фиксируем prefix = 'ERROR'
const error = log.bind(null, 'ERROR')
// Фиксируем prefix = 'INFO'
const info = log.bind(null, 'INFO')
error('Что-то пошло не так')
// "[ERROR] Что-то пошло не так"
info('Сервер запущен')
// "[INFO] Сервер запущен"