Розбираємося з bind і bindAll в Backbone.js

      Користувачі 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);
  }
});

  
Джерело: Хабрахабр

1 коментар

avatar
Дуже дякую за статтю! Приємно читати українською. добавлю в закладки ваш сайт
Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.