Традиционно в JavaScript предоставляются объекты, обладающие собственными свойствами и методами. Например, object1 не может воспользоваться методами object2 и наоборот. 

Однако есть способ обойти это ограничение. 

Вы можете задействовать методы call(), apply() и bind() для привязки функции к объекту и ее вызова, как если бы она принадлежала этому объекту. 

Рассмотрим поочередно данные методы и приведем с ними примеры. 

Метод Call() в JavaScript

Метод call() вызывает функцию с заданным контекстом. Иначе говоря, вы привязываете функцию к объекту, как если бы она ему принадлежала. 

Пример 

Создадим объект obj и функцию add() для сложения одного числа с другим:  

var obj = { num: 2 };function add(a){
  return this.num + a;
}

На данном этапе с add() возникает проблема. Она пытается вернуть this.num вкупе с каким-то значением. Но в функции отсутствует свойство num. Следовательно, вызвать this.num не удается. 

Но, как вы могли заметить, у объекта obj есть свойство num. А что если бы у нас была возможность вызвать функцию add() для объекта, как если бы она ему принадлежала? 

И такая возможность действительно есть. Для этого с помощью метода call() мигом привязываем функцию add() к объекту obj:

add.call(obj, 3);
  • Теперь функция add() получает свой this из obj, к которому она привязана. 
  • При вызове функции add() this.num ссылается на num объекта obj
  • В результате вызов возвращает 5, поскольку 2 + 3 = 5.

Call() с несколькими аргументами 

Возможен вариант использования call() с функциями, принимающими несколько аргументов. 

Пример:

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

console.log(add.call(obj, 3, 5));

Вывод:

10

Метод Apply() в JavaScript

Метод apply() аналогичен call(). Отличие лишь в том, что call() принимает список аргументов, а apply()  —  массив

Пример:

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

console.log(add.apply(obj, [3, 5]));

Вывод:

10

Метод Bind() в JavaScript

В предыдущих разделах мы выяснили суть методов call() и apply(): при вызове они моментально выполняют функцию и возвращают значение. 

Принцип действия метода bind() напоминает call() и apply(), но в отличие от них он возвращает функцию, выполнение которой может быть отложено. 

Скорректируем предыдущий пример, применив bind():

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

const func = add.bind(obj, 3, 5);
func(); // Возвращает 10

Итак, вызов add.bind(obj, 3, 5) возвращает функцию. В этом случае вы присваиваете ее константе func и затем выполняете. 

Здесь вызов func() означает вызов функции add() для объекта obj с аргументами 3, 5

Думаю, принципы работы методов call(), apply() и bind() понятны. Обобщим использование каждого из них с тем же самым объектом и привязанной к нему функцией, рассмотренными ранее. 

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

const resultCall  = add.call(obj, 3, 5);
const resultApply = add.apply(obj, [3, 5]);
const funcBind    = add.bind(obj, 3, 5)
const resultBind  = funcBind();

console.log(resultCall, resultApply, resultBind);

Вывод:

10 10 10

Применение Call() в JavaScript

Настало время посмотреть, как работает метод call() в реальной жизни. 

Call() для создания цепочек конструкторов объектов 

Например, создаем объект Item. Конструктор Item определяется посредством name и price.

Теперь переходим к созданию объектов Car и Fruit, которые являются Items. Но на этот раз их инициализацию проведем не вышеописанным способом, а воспользовавшись объектом Item. Для этого понадобится метод call()

function Item(name, price) {
  this.name = name;
  this.price = price;
  this.description = `${this.name}, ${this.price}€`;
}

function Car(name, price) {
  Item.call(this, name, price);
  // Здесь вы можете добавить другие поля, специфичные для Car 
}

function Fruit(name, price) {
  Item.call(this, name, price);
  // Здесь вы можете добавить другие поля, специфичные для Fruit 
}

const bmw = new Car("BMW", 120000);
const banana = new Fruit("Banana", 1);

Call() для вызова анонимной функции 

Создадим анонимную функцию и посредством call() вызовем ее для каждого объекта массива

Анонимная функция добавляет функцию displayInfo() для каждого объекта массива, что позволяет вывести информацию о занимаемой позиции каждого человека в очереди: 

const queue = [
  { name: 'Matt' },
  { name: 'Jack' }
];

for (let i = 0; i < queue.length; i++) {
  (function(i) {
    this.displayInfo = function() {
      console.log(`Position ${i}: ${this.name}`);
    }
    this.displayInfo();
  }).call(queue[i], i);
}

Вывод:

Position 0: Matt
Position 1: Jack

Call() для выполнения функции с объектом 

С одним таким примером мы уже познакомились при определении call().

Речь идет о привязывании функции к объекту посредством метода call(), благодаря чему вы ее вызываете, как если бы она принадлежала объекту:  

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

console.log(add.call(obj, 3, 5));

Применение Apply() в JavaScript

Apply() для добавления одного массива к другому 

Для добавления элементов в массив потребуется метод push()

При передаче массива в метод push() он добавляет его целиком как один элемент  —  в итоге вы получаете массив в массиве. Здесь можно использовать concat(), однако он создает новый массив.  

Если нужно добавить массив целиком в уже существующий, задействуем apply()

Пример:

const numbers = [1, 2, 3];
const moreNumbers = [4, 5, 6];numbers.push.apply(numbers, moreNumbers);
console.log(numbers);

Вывод:

[1,2,3,4,5,6]

Apply() для создания цепочки конструкторов объектов 

По аналогии с call() можно создавать цепочки конструкторов объектов с помощью apply(). На этот раз передаем массив с информацией в конструктор для инициализации объекта. 

function Item(name, price) {
  this.name = name;
  this.price = price;
  this.description = `${this.name}, ${this.price}€`;
}

function Car(details) {
  Item.apply(this, details);
  // Здесь вы можете добавить другие поля, специфичные для Car
}

function Fruit(details) {
  Item.apply(this, details);
  // Здесь вы можете добавить другие поля, специфичные для Fruit
}

const carDetails = ["BMW", 120000]
const bmw = new Car(carDetails);

const fruitDetails = ["Banana", 2]
const banana = new Fruit(fruitDetails);

Применение Bind() в JavaScript

Bind() для создания связанной функции 

Посредством bind() можно создать функцию, привязанную к объекту. При этом не имеет значения, где и как происходит ее вызов. Главное, что она вызывается вместе с объектом, с которым она связана. 

Рассмотренный ранее пример иллюстрирует как раз этот случай. 

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

const func = add.bind(obj, 3, 5);
func(); // Возвращает 10

Bind() для обеспечения работы SetTimeout 

В этом фрагменте кода кроется проблема: 

let person = {
    name: 'John',
    getName: function() {
        console.log(this.name);
    }
};

window.setTimeout(person.getName, 1000);

Вместо имени “John” он выдает undefined.

Выясним, в чем дело. Для этого перепишем последнюю строку по-другому: 

let func = person.getName;
window.setTimeout(func, 1000);

Когда window вызывает свой метод setTimeout(), то объект this является объектом window. Следовательно, когда setTimeout() вызывает func, которая ссылается на person.getName(), он не располагает информацией об имени person. Для решения этой задачи привяжем функцию к объекту person с помощью метода bind(). В этом случае не имеет значения, где происходит вызов функции,  —  у нее всегда есть доступ к name объекта person.

let func = person.getName.bind(person);
setTimeout(func, 1000);

Вывод:

John

Данный прием работает по следующим причинам: 

  • Метод person.getName присваивается функции func, привязанной к к объекту person
  • Теперь у func есть свой this, указывающий на объект person. При передаче связанной функции в setTimeout() func по-прежнему знает, как получить name объекта person

Заключение 

В JavaScript для привязки функции к объекту можно задействовать методы call(), apply() и bind(). В этом случае функция вызывается для объекта, как если бы она ему принадлежала. 

  • Методы call() и apply() схожи в применении. Они оба сразу же выполняют связанную функцию для объекта. 
  • В отличие от них метод bind() создает и возвращает связанную функцию, выполнение которой можно отложить.

Обобщим использование этих методов в следующем примере: 

var obj = { num: 2 };

function add(a, b){
  return this.num + a + b;
}

const resultCall  = add.call(obj, 3, 5);
const resultApply = add.apply(obj, [3, 5]);
const funcBind    = add.bind(obj, 3, 5)
const resultBind  = funcBind();

console.log(resultCall, resultApply, resultBind);

Благодарю за внимание! Надеюсь, информация была полезной. Успехов в программировании! 

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Artturi Jalli: When to Use Bind(), Call(), and Apply() in JavaScript

Предыдущая статьяКак научиться программировать по 8 часов в день
Следующая статья6 полезных приемов для создания интерфейсов