Разбираемся с bind и bindAll в Backbone.js

в 11:57, , рубрики: backbone.js, javascript, Веб-разработка

Пользователи Backbone.js часто используют bind и bindAll методы предоставленные им библиотекой Underscore.js. В этом блоге я собираюсь обсудить зачем нужны эти методы и как они работают.

Все начинается с apply

Функция bindAll использует внутри себя bind. А bind в свою очередь использует apply. Поэтому важно понять, что делает apply.

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func();

Если я выполню выше написанный код, то я получу "[object window] is beautiful". Я получаю такое сообщение, потому что когда функция вызвана, то this равен window, глобальному объекту по умолчанию.

Для того что бы изменить значение this, мы может использовать метод apply как показано ниже.

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func.apply('Internet');

В приведенном выше случае будет выведено сообщение «Internet is beautiful». Аналогичным образом следующий код произведет «Beach is beautiful».

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func.apply('Beach'); //Beach is beautiful

Короче говоря, apply позволяет нам контролировать значение this когда функция вызывается.

Зачем нужен bind

Для того что бы понять зачем нужен bind метод, сперва давайте посмотрим на следующий пример.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
john.says(); //Ruby rocks!

Пример выше довольно прямолинеен. Объект john является экземпляром Developer и когда функция says вызывается, мы получим правильное оповещающее сообщение.

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

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = john.says;
func();// undefined rocks!

Код выше похож на код расположенный над ним. Все что мы сделали это сохранили функцию в переменную с именем func. Если мы вызываем эту функцию, то мы должны получить сообщение которое ожидаем. Однако, если мы запустим этот код, то оповещающее сообщение будет «undefined rocks!».

Мы получаем «undefined rocks!», потому что в этом случае функция func была вызвана в глобальном контексте. Когда функция выполняется, this указывает на глобальный объект под названием window. И у объекта window нет никакого свойства с именем skill. Таким образом, выводимое значение this.skill это undefined.

Ранее мы видели, что используя apply, мы можем решить проблему возникающую из-за this. Так давайте попробуем использовать для решения apply.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = john.says;
func.apply(john);

Код выше решает нашу проблему. На этот раз полученное нами оповещающее сообщение было «Ruby rocks!». Однако существует проблема и немаленькая.

В JavaScript мире функции являются гражданами первого класса. Причина, по которой мы создаем функцию состоит в том, что мы легко можем везде ее передать. В описанном выше случае мы создали функцию с именем func. Однако наряду с функцией func теперь мы должны везде передавать переменную john. Это не очень хорошая идея. Во-вторых, ответственность за правильный вызов этой функции была перенесена из функции создателя в функцию потребителя. Это не очень хорошее API.

Мы должны попробовать создать функции, которые можно будет легко вызвать их потребителями. И тут в дело вступает bind.

Как bind решает проблему

Во-первых, давайте посмотрим как использование bind решает проблему.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = _.bind(john.says, john);
func();// Ruby rocks!

Чтобы решить проблему касающуюся this, нам нужна функция, которая уже сопоставлена с john таким образом, что бы нам не пришлось везде заботиться о ней. Это точно то, что делает метод bind. Он возвращает новую функцию и значение ее this становится тем, которое мы предоставили.

Вот кусок кода из bind метода:

return function() {
  return func.apply(obj, args.concat(slice.call(arguments)));
};

Как вы можете видеть внутри себя bind использует apply, что бы установить this вторым параметром, который был передан нами при вызове bind.

Обратите внимание, что bind не изменяет существующую функцию. Он возвращает новую функцию и использовать нужно ее.

Как bindAll решает проблему

Вместо bind мы также можем использовать bindAll. Вот решение с bindAll.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
_.bindAll(john, 'says');
var func = john.says;
func(); //Ruby rocks!

Код выше похож на решение с bind, но есть несколько больших различий. Первое различие это то, что нам не нужно переживать за возвращаемое значение bindAll. В случае с bind мы должны использовать возвращаемую функцию. В bindAll мы не переживаем за возвращаемое значение, но за это нужно заплатить свою цену. На самом деле bindAll видоизменяет функцию. Что это значит?

Видите что объект john имеет свойство с именем says, которое возвращает функцию. Метод bindAll изменяет свойство says, таким образом, что когда он возвращает функцию, она уже связана с объектом john.

Вот фрагмент кода из метода bindAll:

function(f) { obj[f] = _.bind(obj[f], obj); }

Заметьте, что bindAll внутри себя вызывает метод bind и он замещает значение существующего свойства функцией которую вернул bind.

Другое различие между bind и bindAll это то, что в bind первый параметр функция john.says, а второй объект john. В bindAll первый параметр объект john и второй параметр не функция, а имя свойства.

На что обратить внимание

При разработке Backbone.js приложений кто-то писал код следующим образом:

window.ProductView = Backbone.View.extend({
  initialize: function() {
    _.bind(this.render, this);
    this.model.bind('change', this.render);
  }
});

Код выше не будет работать, потому что возвращаемое значение bind не используется. Правильное использование будет таким:

window.ProductView = Backbone.View.extend({
  initialize: function() {
    this.model.bind('change', _.bind(this.render, this));
  }
});

Или вы можете использовать bindAll как показано ниже:

window.ProductView = Backbone.View.extend({
  initialize: function() {
    _.bindAll(this, this.render);
    this.model.bind('change', this.render);
  }
});

Автор: akolomiets

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js