Функции call(), apply() и bind() в JavaScript
JavaScript

Функции call(), apply() и bind() в JavaScript

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

В статье мы рассмотрим случаи, когда эти три метода стоит использовать. Нужно иметь ввиду, что методы call() и apply() являются частью стандарта ECMAScript 3, тогда как bind() добавился в стандарте ECMAScript 5 (работает только в современных браузерах, в частности, не работает в IE<9).

Метод bind()

Метод bind() используется преимущественно для того, чтобы вызвать функцию с явным указанием значения this. Другими словами, bind() позволяет нам указать, ссылка на какой объект будет значением this, когда функция будет вызвана, и вызвать эту функцию.

Это может показаться тривиальной задачей, но иногда действительно необходимо явно указать значение this. Проблема может быть в том, что функция не всегда вызывается в контексте того объекта, в котором она описана — соответственно значение this в функции может меняться, что вызывает, как правило, различные неприятные ошибки.

Рассмотрим пример явного связывания значения this с объектом:


var users = {
  data: [
      {name: 'John Smith'},
      {name: 'Ellen Simons'}
  ],

   showFirst: function (event) {
       console.log(this.data[0].name);
   }
}
 
$('button').click(users.showFirst); // this.data is undefined

В данном примере значением this в функции clickHandler будет выступать уже не объект users, как мы того хотели, а объект кнопки, поэтому и свойство data у него отсутствует.

Решить эту проблему можно, используя метод bind().


$("button").click(users.showFirst.bind(users));

Поскольку метод bind() является частью стандарта ES5, этот способ не работает в браузерах IE<9. Если необходима поддержка этих браузеров, можно использовать реализацию функции bind(), предложенную Дугласом Крокфордом:


if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
            throw new TypeError ("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function (){},
            fBound = function () {
                return fToBind.apply(this instanceof fNOP && oThis
                    ? this
                    : oThis,
                    aArgs.concat (Array.prototype.slice.call (arguments)));
                };

                fNOP.prototype = this.prototype;
                fBound.prototype = new fNOP ();

                return fBound;
            };
    }
}

Использование bind() для заимствования методов

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


var cars = {
    data:[
        { name: ‘Mitzubisi Lancer’ },
        { name: ‘Chevrolet Impala’ }
    ]
}

cars.showFirst = users.showFirst.bind(cars);
cars.showFirst();

Использование bind() в данном случае позволяет избежать проблем с изменением значения this.

Использование bind() при каррировании

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

Рассмотрим на примере каррирование функции с помощью bind():


// Определим функцию от трех переменных
function greet(gender, age, name) {
    // if a male, use Mr., else use Ms.
    var salutation = gender === "male" ? "Mr. " : "Ms. ";

    if (age > 25) {
        return "Hello, " + salutation + name + ".";
    }
    else {
        return "Hey, " + name + ".";
    }
}

// C помощью bind() мы можем получать функции от меньшего числа переменных
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."

Таким образом, с помощью функции bind() мы можем получать из текущей функции различные функции от меньшего числа параметров. Мы рассмотрим каррирование более подробно в одной из следующих статей, так как это довольно мощный и интересный прием функционального программирования, нередко используемый в JavaScript.

Методы call() и apply()

Методы apply() и call() — это два не менее важных метода объекта Function, которые также позволяют явно установить значение this для функции.

Рассмотрим пример:


var avgScore = "global avgScore";

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

    this.avgScore = sumOfScores / arrayOfScores.length;
}

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

avg(gameController.scores);
console.log(window.avgScore); // 46.4
console.log(gameController.avgScore); // null

avgScore = "global avgScore";
avg.call(gameController, gameController.scores);

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

В данном примере первый параметр метода call() используется в качестве значения this, а остальные — передаются функции как параметры. Метод apply() аналогичен методу call(), первый параметр также используется в качестве значения this, а остальные передаются в виде массива.

Снова обратимся к примеру:


var clientData = {
    id: 094545,
    fullName: "Not Set",
    setUserName: function(firstName, lastName)  {   
        this.fullName = firstName + " " + lastName;
    }
}

function getUserInput(firstName, lastName, callback, callbackObj) {
    callback.apply(callbackObj, [firstName, lastName]);
}

В данном примере метод apply() позволяет нам выполнять функцию обратного вызова в контексте переданного объекта.


getUserInput ("John", "Smith", clientData.setUserName, clientData);
console.log (clientData.fullName);

Заимствование методов с помощью call() и apply()

Одним из наиболее частых прменений методов call() и apply() является заимствование методов. Примером может служить заимствование методов массива, например:


var data = {0:"Martin", 1:78, 2:67, 3:["Letta", "Marieta", "Pauline"], length:4};
           
var newArray = Array.prototype.slice.call(data, 0);
console.log (newArray); // ["Martin", 78, 67, Array[3]]

console.log(Array.prototype.indexOf.call(data, "Martin") === -1 ? false : true); // true

console.log(data.indexOf ("Martin") === -1 ? false : true);
console.log(Array.prototype.reverse.call(data));
console.log(Array.prototype.pop.call(data));
console.log(data);

console.log(Array.prototype.push.call(data, "Jackie"));
console.log(data); // {0: Array[3], 1: 67, 2: 78, 3: "Jackie", length: 4}

Если нужно получить список переданных параметров в виде массива:


function doSomething () {
    var args = Array.prototype.slice.call(arguments);
    console.log(args);
}

doSomething("Water", "Salt", "Glue"); // ["Water", "Salt", "Glue"]

Также можно заимствовать методы любых других объектов.

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


function welcomeStudents() {
    var args     = Array.prototype.slice.call (arguments),
        lastItem = args.pop();

    console.log("Welcome " + args.join (", ") + ", and " + lastItem + ".");
}
welcomeStudents.apply (null, students);

Заключение

Мы рассмотрели наиболее частые случаи и особенности использования методов call(), apply() и bind(). Все эти функции применяются для явного указания значения this в функции: при заимствовании метода, при передаче функции обратного вызова. С помощью методов Function.prototype также можно использовать такие приемы как каррирование или функции от произвольного числа параметров.

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться