Подробно о методах apply(), call() и bind(), необходимых каждому JavaScript разработчику

Stas Bagretsov
Jan 13 · 13 min read

Метод bind()

Bind() позволяет нам выставить значение this для метода

var user = {
data: [
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler: function (event) {
var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // Случайное число от 0 до 1.

// Эта строка добавляет случайного человека из массива данных в текстовое поле.
$("input").val(this.data[randomNum].name + " " + this.data[randomNum].age);
}

}

// Назначаем eventHandler на событие по клику на кнопку
$ ("button").click (user.clickHandler);
$ ("button").click (user.clickHandler);
$ ("button").click (user.clickHandler.bind (user));
// Переменная data является глобальной
var data = [
{name:"Samantha", age:12},
{name:"Alexis", age:14}
]

var user = {
// а это уже локальная
data: [
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
showData: function (event) {
var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // Любое число с 0 до 1

console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
}

}

// Назначаем метод showData от объекта переменной
var showDataVar = user.showData;

showDataVar (); // Samantha 12 (Данные взялись из глобального массива данных, а не из локального в объекте)
//  Привязываем метод showData к объекту user
var showDataVar = user.showData.bind (user);

// Теперь мы получаем значение из объекта user, так как this привязано к объекту
showDataVar (); // P. Mickelson 43

С помощью bind() мы можем заимствовать методы

//  Тут у нас объект с данными о машинах, у которого нет метода для вывода своих данных в консоль
var cars = {
data:[
{name:"Honda Accord", age:14},
{name:"Tesla Model S", age:2}
]

}

// Мы можем взять метод showData() из объекта user, который мы сделали в предыдущем примере
// Ниже мы свяжем метод user.showData с объектом cars
cars.showData = user.showData.bind (cars);
cars.showData (); // Honda Accord 14

С помощью bind() мы можем каррировать функцию

function greet (gender, age, name) {
// Если мужчина, то используем Mr., если нет то Ms..
var salutation = gender === "male" ? "Mr. " : "Ms. ";

if (age > 25) {
return "Hello, " + salutation + name + ".";
}
else {
return "Hey, " + name + ".";
}
}
//  В общем, мы передаем null, так как мы не используем this в функции
var greetAnAdultMale = greet.bind (null, "male", 45);

greetAnAdultMale ("John Hartlove"); // "Hello, Mr. John Hartlove."

var greetAYoungster = greet.bind (null, "", 16);
greetAYoungster ("Alex"); // "Hey, Alex."
greetAYoungster ("Emma Waterloo"); // "Hey, Emma Waterloo."

Методы Apply и Call

Выставляем значение this с помощью Apply или Call

//  Глобальная переменная для демонстрации
var avgScore = "global avgScore";

// Функция
function avg (arrayOfScores) {
// Складываем все показатели
var sumOfScores = arrayOfScores.reduce (function (prev, cur, index, array) {
return prev + cur;
});

// В этом случае this будет привязан к глобальному объекту, пока мы не выставим его с call() или apply()
this.avgScore = sumOfScores / arrayOfScores.length;
}

var gameController = {
scores: [20, 34, 55, 46, 77],
avgScore:null
}

// Если мы выполним функцию avg, то this внутри функции будет привязана к глобальному объекту:
avg (gameController.scores);
// Вот, что получаем:
console.log (window.avgScore); // 46.4
console.log (gameController.avgScore); // null

// Сбрасываем avgScore
avgScore = "global avgScore";

// Чтобы указать, что значение this привязано к gameController,
// Мы вызываем call() метод:
avg.call (gameController, gameController.scores);

console.log (window.avgScore); //global avgScore
console.log (gameController.avgScore); // 46.4

Используем call() и apply(), чтобы выставлять this в Callback функциях

//  Создаём объект со свойствами и методами
// Далее мы передадим метод, как колбэк другой функции
var clientData = {
id: 094545,
fullName: "Not Set",
// Метод на объекте clientData
setUserName: function (firstName, lastName) {
// тут мы выставляем fullName свойство в данном объекте
this.fullName = firstName + " " + lastName;
}
}
function getUserInput (firstName, lastName, callback, callbackObj) {
// Использование метода apply ниже, выставит this для callbackObj
callback.apply (callbackObj, [firstName, lastName]);
}
// Объект clientData будет использоваться методом Apply, чтобы выставить значение this.
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
// Получаем в консоль
console.log (clientData.fullName); // Barack Obama

Заимствование функций с помощью Apply и Call (Важно знать)

Заимствуем методы массива

//  Массивоподобный объект: тут обратите внимание на то, что неотрицательные целые числа используются как ключи
var anArrayLikeObj = {0:"Martin", 1:78, 2:67, 3:["Letta", "Marieta", "Pauline"], length:4 };
//  Делаем быструю копию и сохраняем её в реальный объект:
// Первый параметр выставляет значение “this”
var newArray = Array.prototype.slice.call (anArrayLikeObj, 0);

console.log (newArray); // ["Martin", 78, 67, Array[3]]

// Ищем Мартина в нашем массивоподобном объекте
console.log (Array.prototype.indexOf.call (anArrayLikeObj, "Martin") === -1 ? false : true); // true

// А теперь давайте применим indexOf без вызова call() или apply()
console.log (anArrayLikeObj.indexOf ("Martin") === -1 ? false : true); // Error: Object has no method 'indexOf'

// Переворачиваем объект:
console.log (Array.prototype.reverse.call (anArrayLikeObj));
// {0: Array[3], 1: 67, 2: 78, 3: "Martin", length: 4}

// Клёво, мы даже можем вызвать pop():
console.log (Array.prototype.pop.call (anArrayLikeObj));
console.log (anArrayLikeObj); // {0: Array[3], 1: 67, 2: 78, length: 3}

// А что у нас с push()?
console.log (Array.prototype.push.call (anArrayLikeObj, "Jackie"));
console.log (anArrayLikeObj); // {0: Array[3], 1: 67, 2: 78, 3: "Jackie", length: 4}
function transitionTo (name) {
// Так как объект arguments это массивоподобный объект
// Мы можем использовать на нём slice ()
// Число один в параметре говорит о том, нужно отдать копию массива от параметра с индексом 1 и до конца. Или простым языком, просто пропустить первый элемент.

var args = Array.prototype.slice.call (arguments, 1);

// Я добавил этот кусочек кода, чтобы мы могли видеть то, что получится в args.

// Я закомментировал последнюю строку, потому что она не в тему этого примера.
//doTransition(this, name, this.updateURL, args);
}

// Так как метод slice() скопировал все элементы начиная от индекса 1 до конечного, первый элемент “contact” не был отдан.
transitionTo ("contact", "Today", "20"); // ["Today", "20"]
//  Мы пока не определяем функцию с какими-либо параметрами, но можем получить все переданные ей аргументы
function doSomething () {
var args = Array.prototype.slice.call (arguments);
console.log (args);
}

doSomething ("Water", "Salt", "Glue"); // ["Water", "Salt", "Glue"]
var gameController = {
scores :[20, 34, 55, 46, 77],
avgScore:null,
players: [
{name:"Tommy", playerID:987, age:23},
{name:"Pau", playerID:87, age:33}
]
}

var appController = {
scores: [900, 845, 809, 950],
avgScore:null,
avg :function () {

var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {
return prev + cur;
});

this.avgScore = sumOfScores / this.scores.length;
}
}

// Обратите внимание, что тут мы используем apply(), так что вторым аргументом должен быть массив
appController.avg.apply (gameController);
console.log (gameController.avgScore); // 46.4

// appController.avgScore до сих пор null; он не изменился, только gameController.avgScore
console.log (appController.avgScore); // null
appController.maxNum = function () {
this.avgScore = Math.max.apply (null, this.scores);
}

appController.maxNum.apply (gameController, gameController.scores);
console.log (gameController.avgScore); // 77

Используем apply() для выполнения функций с составными переменными

createAccount (arrayOfItems[0], arrayOfItems[1], arrayOfItems[2], arrayOfItems[3]);
//  Мы можем передать любое число аргументов Math.max () методу
console.log (Math.max (23, 11, 34, 56)); // 56
var allNumbers = [23, 11, 34, 56];
// Попросту мы не можем передать массив чисел методу
console.log (Math.max (allNumbers)); // NaN
var allNumbers = [23, 11, 34, 56];
// Используя метод apply(), мы передаём числа:
console.log (Math.max.apply (null, allNumbers)); // 56
var students = ["Peter Alexander", "Michael Woodruff", "Judy Archer", "Malcolm Khan"];

// Не указываем конкретное число параметров, так как любое число параметров допустимо
function welcomeStudents () {
var args = Array.prototype.slice.call (arguments);

var lastItem = args.pop ();
console.log ("Welcome " + args.join (", ") + ", and " + lastItem + ".");
}

welcomeStudents.apply (null, students);
// Welcome Peter Alexander, Michael Woodruff, Judy Archer, and Malcolm Khan.

Заключение

Stas Bagretsov

Written by

Надеюсь верую вовеки не придет ко мне позорное благоразумие. webdev/sports/books

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade