# Привязка контекста и карринг: "bind"
Функции в JavaScript никак не привязаны к своему контексту `this`, с одной стороны, здорово -- это позволяет быть максимально гибкими, одалживать методы и так далее.
Но с другой стороны -- в некоторых случаях контекст может быть потерян. То есть мы вроде как вызываем метод объекта, а на самом деле он получает `this = undefined`.
Такая ситуация является типичной для начинающих разработчиков, но бывает и у "зубров" то же. Конечно, зубры при этом знают, что с ней делать.
[cut]
## Пример потери контекста
В браузере есть встроенная функция `setTimeout(func, ms)`, которая вызывает выполение функции `func` через `ms` миллисекунд (=1/1000 секунды).
Мы подробно остановимся на ней и её тонкостях позже, в главе [](/setTimeout-setInterval), а пока просто посмотрим пример.
Этот код выведет "Привет" через 1000мс, то есть 1 секунду:
```js
//+ run
setTimeout(function() {
alert("Привет");
}, 1000);
```
Попробуем сделать то же самое с методом объекта, следующий код должен выводить имя пользователя через 1 секунду:
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function() {
alert(this.firstName);
}
};
*!*
setTimeout( user.sayHi, 1000); // undefined (не Вася!)
*/!*
```
**При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`!**
Это произошло потому, что в примере выше `setTimeout` получил функцию `user.sayHi`, но не её контекст. То есть, последняя строчка аналогична двум таким:
```js
var f = user.sayHi;
setTimeout(f, 1000); // контекст user потеряли
```
**Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно?**
Есть несколько способов решения, среди которых мы, в зависимости от ситуации, можем выбирать.
## Решение 1: сделать обёртку
**Самый простой вариант решения -- это обернуть вызов в анонимную функцию:**
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function() {
alert(this.firstName);
}
};
*!*
setTimeout(function() {
user.sayHi(); // Вася
}, 1000);
*/!*
```
Теперь код работает, так как `user` достаётся из замыкания.
Но тут же появляется и уязвимое место в структуре кода!
**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!**
Хорошо бы гарантировать правильность контекста.
## Решение 2: bind для привязки контекста
Напишем вспомогательную функцию `bind(func, context)`, которая будет жёстко фиксировать контекст для `func`:
```js
function bind(func, context) {
return function() {
return func.apply(context, arguments);
};
}
```
Параметры:
- `func`
- Произвольная функция
- `context`
- Произвольный объект
Результатом вызова `bind(func, context)` будет, как видно из кода, функция-обёртка, которая передаёт все вызовы `func`, указывая при этом правильный контекст `context`.
Чтобы понять, как она работает, нужно вспомнить тему "замыкания" -- здесь `bind` возвращает анонимную функцию, которая при вызове получает контекст `context` из внешней области видимости и передаёт вызов в `func` вместе с этим контекстом `context` и аргументами `arguments`.
**В результате вызова `bind` мы получаем как бы "ту же функцию, но с фиксированным контекстом".**
Пример с `bind`:
```js
//+ run
function bind(func, context) {
return function() {
return func.apply(context, arguments);
};
}
var user = {
firstName: "Вася",
sayHi: function() {
alert(this.firstName);
}
};
*!*
setTimeout( bind(user.sayHi, user), 1000 );
*/!*
```
Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст.
## Решение 3: встроенный метод bind [#bind]
В современном JavaScript (или при подключении библиотеки [es5-shim](https://github.com/kriskowal/es5-shim) для IE8-) у функций уже есть встроенный метод [bind](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind), который мы можем использовать.
Он позволяет получить обёртку, которая привязывает функцию не только к нужному контексту но, если нужно, то и к аргументам.
Синтаксис `bind`:
```js
var wrapper = func.bind(context[, arg1, arg2...])
```
- `func`
- Произвольная функция
- `context`
- Обертка `wrapper` будет вызывать функцию с контекстом `this = context`.
- `arg1`, `arg2`, ...
- Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.
Результат вызова: `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, но это делается редко, мы поговорим о них позже.
Пример со встроенным методом `bind`:
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function() {
alert(this.firstName);
}
};
*!*
// setTimeout( bind(user.sayHi, user), 1000 );
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
*/!*
```
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
[smart header="Привязать всё: `bindAll`"]
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
```js
for(var prop in user) {
if (typeof user[prop] == 'function') {
user[prop] = user[prop].bind(user);
}
}
```
В некоторых JS-фреймворках есть даже встроенные функции для этого, например [_.bindAll(obj)](http://lodash.com/docs#bindAll).
[/smart]
## Карринг
До этого мы говорили о привязке контекста. Теперь пойдём на шаг дальше. Привязывать можно не только контекст, но и аргументы. Используется это реже, но бывает полезно.
[Карринг](http://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) (currying) или *каррирование* -- термин [функционального программирования](http://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), который означает создание новой функции путём фиксирования аргументов существующей.
Как было сказано выше, метод `func.bind(context, ...)` может создавать обёртку, которая фиксирует не только контекст, но и ряд аргументов функции.
Например, есть функция умножения двух чисел `mul(a, b)`:
```js
function mul(a, b) {
return a * b;
};
```
При помощи `bind` создадим функцию `double`, удваивающую значения. Это будет вариант функции `mul` с фиксированным первым аргументом:
```js
//+ run
*!*
// double умножает только на два
var double = mul.bind(null, 2); // контекст фиксируем null, он не используется
*/!*
alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10
```
При вызове `double` будет передавать свои аргументы исходной функции `mul` после тех, которые указаны в `bind`, то есть в данном случае после зафиксированного первого аргумента `2`.
**Говорят, что `double` является "частичной функцией" (partial function) от `mul`.**
Другая частичная функция `triple` утраивает значения:
```js
//+ run
*!*
var triple = mul.bind(null, 3); // контекст фиксируем null, он не используется
*/!*
alert( triple(3) ); // = mul(3, 3) = 9
alert( triple(4) ); // = mul(3, 4) = 12
alert( triple(5) ); // = mul(3, 5) = 15
```
**При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.**
## Задачи
Рассмотрим для дальнейших задач "функцию для вопросов" `ask`:
```js
function ask(question, answer, ok, fail) {
var result = prompt(question, '');
if (result.toLowerCase() == answer.toLowerCase()) ok();
else fail();
}
```
Пока в этой функции ничего особого нет. Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
Однако, тем не менее, эта функция взята из реального проекта. Просто обычно она сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
Пример использования:
```js
//+ run
*!*
ask("Выпустить птичку?", "да", fly, die);
*/!*
function fly() {
alert('улетела :)');
}
function die() {
alert('птичку жалко :(');
}
```
## Итого
Функции и контекст к JavaScript -- как шнурки и кроссовки. Если мы куда-то отправляем шнурки (например в `setTimeout`), то кроссовки сами за ними не побегут.
Нужно либо передать их дополнительно, либо привязать одно к другому вызовом `bind`, либо завернуть в замыкание.
[head]
[/head]