init
This commit is contained in:
parent
06f61d8ce8
commit
f301cb744d
2271 changed files with 103162 additions and 0 deletions
|
@ -0,0 +1,8 @@
|
|||
|
||||
Страшновато выглядит, да? Работает так (по строкам):
|
||||
<ol>
|
||||
<li>Вызов `bind` сохраняет дополнительные аргументы `args` (они идут со 2го номера) в массив `bindArgs`.</li>
|
||||
<li>... и возвращает обертку `wrapper`.</li>
|
||||
<li>Эта обёртка делает из `arguments` массив `args` и затем, используя метод [concat](http://javascript.ru/Array/concat), прибавляет их к аргументам `bindArgs` (карринг).</li>
|
||||
<li>Затем передаёт вызов `func` с контекстом и общим массивом аргументов.</li>
|
||||
</ol>
|
22
01-js/06-objects-more/05-bind/01-cross-browser-bind/task.md
Normal file
22
01-js/06-objects-more/05-bind/01-cross-browser-bind/task.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Кросс-браузерная эмуляция bind
|
||||
|
||||
[importance 3]
|
||||
|
||||
Если вы вдруг захотите копнуть поглубже -- аналог `bind` для IE8- и старых версий других браузеров будет выглядеть следующим образом:
|
||||
|
||||
```js
|
||||
function bind(func, context /*, args*/) {
|
||||
var bindArgs = [].slice.call(arguments, 2); // (1)
|
||||
function wrapper() { // (2)
|
||||
var args = [].slice.call(arguments);
|
||||
var unshiftArgs = bindArgs.concat(args); // (3)
|
||||
return func.apply(context, unshiftArgs); // (4)
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
```
|
||||
|
||||
Использование -- вместо `mul.bind(null, 2)` вызывать `bind(mul, null, 2)`.
|
||||
|
||||
Не факт, что он вам понадобится, но в качестве упражнение попробуйте разобраться, как это работает.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Ответ: `Hello`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function f() {
|
||||
alert( this );
|
||||
}
|
||||
|
||||
var user = {
|
||||
g: bind(f, "Hello")
|
||||
}
|
||||
|
||||
user.g();
|
||||
```
|
||||
|
||||
Так как вызов идёт в контексте объекта `user.g()`, то внутри функции `g` контекст `this = user`.
|
||||
|
||||
Однако, функции `g` совершенно без разницы, какой `this` она получила.
|
||||
|
||||
Её единственное предназначение -- это передать вызов в `f` вместе с аргументами и ранее указанным контекстом `"Hello"`, что она и делает.
|
||||
|
||||
Эта задача демонстрирует, что изменить однажды привязанный контекст уже нельзя.
|
|
@ -0,0 +1,18 @@
|
|||
# Запись в объект после bind
|
||||
|
||||
[importance 5]
|
||||
|
||||
Что выведет функция?
|
||||
|
||||
```js
|
||||
function f() {
|
||||
alert( this );
|
||||
}
|
||||
|
||||
var user = {
|
||||
g: bind(f, "Hello")
|
||||
}
|
||||
|
||||
user.g();
|
||||
```
|
||||
|
55
01-js/06-objects-more/05-bind/03-second-bind/solution.md
Normal file
55
01-js/06-objects-more/05-bind/03-second-bind/solution.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
Ответ: `"Вася"`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function f() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
f = f.bind( {name: "Вася"} ).bind( {name: "Петя"} );
|
||||
|
||||
f(); // Вася
|
||||
```
|
||||
|
||||
Первый вызов `f.bind(..Вася..)` возвращает "обёртку", которая устанавливает контекст для `f` и передаёт вызов `f`.
|
||||
|
||||
Следующий вызов `bind` будет устанавливать контекст уже для этой обёртки, это ни на что не влияет.
|
||||
|
||||
Чтобы это проще понять, используем наш собственный вариант `bind` вместо встроенного:
|
||||
|
||||
```js
|
||||
function bind(func, context) {
|
||||
return function() {
|
||||
return func.apply(context, arguments);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Код станет таким:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
f = bind(f, {name: "Вася"} ); // (1)
|
||||
f = bind(f, {name: "Петя"} ); // (2)
|
||||
|
||||
f(); // Вася
|
||||
```
|
||||
|
||||
Здесь видно, что первый вызов `bind`, в строке `(1)`, возвращает обёртку вокруг `f`, которая выглядит так (выделена):
|
||||
|
||||
```js
|
||||
function bind(func, context) {
|
||||
*!*
|
||||
return function() {
|
||||
return func.apply(context, arguments);
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
В этой обёртке нигде не используется `this`, только `func` и `context`. Посмотрите на код, там нигде нет `this`.
|
||||
|
||||
Поэтому следующий `bind` в строке `(2)`, который выполняется уже над обёрткой и фиксирует в ней `this`, ни на что не влияет. Какая разница, что будет в качестве `this` в функции, которая этот `this` не использует?
|
16
01-js/06-objects-more/05-bind/03-second-bind/task.md
Normal file
16
01-js/06-objects-more/05-bind/03-second-bind/task.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Повторный bind
|
||||
|
||||
[importance 5]
|
||||
|
||||
Что выведет этот код?
|
||||
|
||||
```js
|
||||
function f() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } );
|
||||
|
||||
f();
|
||||
```
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Ответ: `undefined`.
|
||||
|
||||
Результатом работы `bind` является функция-обёртка над `sayHi`. Эта функция -- самостоятельный объект, у неё уже нет свойства `test`.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Свойство функции после bind
|
||||
|
||||
[importance 5]
|
||||
|
||||
В свойство функции записано значение. Изменится ли оно после применения `bind`? Обоснуйте ответ.
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
alert(this.name);
|
||||
}
|
||||
sayHi.test = 5;
|
||||
alert(sayHi.test); // 5
|
||||
|
||||
*!*
|
||||
var bound = sayHi.bind({ name: "Вася" });
|
||||
|
||||
alert(bound.test); // что выведет? почему?
|
||||
*/!*
|
||||
```
|
||||
|
105
01-js/06-objects-more/05-bind/05-question-use-bind/solution.md
Normal file
105
01-js/06-objects-more/05-bind/05-question-use-bind/solution.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
# Решение с bind
|
||||
|
||||
Ошибка происходит потому, что `ask` получает только функцию, без объекта-контекста.
|
||||
|
||||
Используем `bind`, чтобы передать в `ask` функцию с уже привязанным контекстом:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginOk: function() {
|
||||
alert(this.login + ' вошёл в сайт');
|
||||
},
|
||||
|
||||
loginFail: function() {
|
||||
alert(this.login + ': ошибка входа');
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password, this.loginOk.bind(this), this.loginFail.bind(this));
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
var vasya = user;
|
||||
user = null;
|
||||
vasya.checkPassword();
|
||||
```
|
||||
|
||||
# Решение через замыкание
|
||||
|
||||
Альтернативное решение -- сделать функции-обёртки над `user.loginOk/loginFail`:
|
||||
|
||||
```js
|
||||
var user = {
|
||||
...
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() { user.loginOk(); }, function() { user.loginFail(); });
|
||||
*/!*
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
...Но такой код использует переменную `user`, так что если объект переместить из неё, к примеру, так, то работать он не будет:
|
||||
|
||||
```js
|
||||
var vasya = user; // переместим user в vasya
|
||||
user = null;
|
||||
vasya.checkPassword(); // упс будет ошибка, ведь в коде объекта остался user
|
||||
```
|
||||
|
||||
Для того, чтобы избежать проблем, можно использовать `this`. Внутри `checkPassword` он всегда будет равен текущему объекту, так что скопируем его в переменную, которую назовём `self`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginOk: function() {
|
||||
alert(this.login + ' вошёл в сайт');
|
||||
},
|
||||
|
||||
loginFail: function() {
|
||||
alert(this.login + ': ошибка входа');
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
var self = this;
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() { self.loginOk(); },
|
||||
function() { self.loginFail(); }
|
||||
);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
var vasya = user;
|
||||
user = null;
|
||||
vasya.checkPassword();
|
||||
```
|
||||
|
||||
Теперь всё работает. Анонимные функции достают правильный контекст из замыкания, где он сохранён в переменной `self`.
|
50
01-js/06-objects-more/05-bind/05-question-use-bind/task.md
Normal file
50
01-js/06-objects-more/05-bind/05-question-use-bind/task.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Использование функции вопросов
|
||||
|
||||
[importance 5]
|
||||
|
||||
Вызов `user.checkPassword()` в коде ниже должен, при помощи `ask`, спрашивать пароль и вызывать `loginOk/loginFail` в зависимости от правильности ответа.
|
||||
|
||||
Однако, его вызов приводит к ошибке. Почему?
|
||||
|
||||
Исправьте выделенную строку, чтобы всё работало (других строк изменять не надо).
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginOk: function() {
|
||||
alert(this.login + ' вошёл в сайт');
|
||||
},
|
||||
|
||||
loginFail: function() {
|
||||
alert(this.login + ': ошибка входа');
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password, this.loginOk, this.loginFail);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
P.S. Ваше решение должно также срабатывать, если переменная `user` будет перезаписана, например вместо `user.checkPassword()` в конце будут строки:
|
||||
|
||||
```js
|
||||
var vasya = user;
|
||||
user = null;
|
||||
vasya.checkPassword();
|
||||
```
|
||||
|
69
01-js/06-objects-more/05-bind/06-ask-currying/solution.md
Normal file
69
01-js/06-objects-more/05-bind/06-ask-currying/solution.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Решение с bind
|
||||
|
||||
Первое решение -- передать в `ask` функции с привязанным контекстом и аргументами.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginDone: function(result) {
|
||||
alert(this.login + (result ? ' вошёл в сайт' : ' ошибка входа'));
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password, this.loginDone.bind(this, true), this.loginDone.bind(this, false));
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
# Решение с локальной переменной
|
||||
|
||||
Второе решение -- это скопировать `this` в локальную переменную (чтобы внешняя перезапись не повлияла):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginDone: function(result) {
|
||||
alert(this.login + (result ? ' вошёл в сайт' : ' ошибка входа'));
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
var self = this;
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() { self.loginDone(true); },
|
||||
function() { self.loginDone(false); }
|
||||
);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
Оба решения хороши, вариант с `bind` короче.
|
53
01-js/06-objects-more/05-bind/06-ask-currying/task.md
Normal file
53
01-js/06-objects-more/05-bind/06-ask-currying/task.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Использование функции вопросов с каррингом
|
||||
|
||||
[importance 5]
|
||||
|
||||
Эта задача -- усложнённый вариант задачи [](/task/question-use-bind). В ней объект `user` изменён.
|
||||
|
||||
Теперь вместо двух функций `user.loginOk()` и `user.loginFail()` теперь один метод: `user.loginDone(true/false)`, который нужно вызвать с `true` при верном ответе и `fail` -- при неверном.
|
||||
|
||||
Код ниже делает это, соответствующий фрагмент выделен.
|
||||
|
||||
**Сейчас он обладает важным недостатком: при записи в `user` другого значения объект перестанет корректно работать, вы увидите это, запустив пример ниже (будет ошибка).**
|
||||
|
||||
Как бы вы написали правильно?
|
||||
|
||||
**Исправьте выделенный фрагмент, чтобы код заработал.**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
// метод для вызова из ask
|
||||
loginDone: function(result) {
|
||||
alert(this.login + (result ? ' вошёл в сайт' : ' ошибка входа'));
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() { user.loginDone(true); },
|
||||
function() { user.loginDone(false); }
|
||||
);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
var vasya = user;
|
||||
user = null;
|
||||
vasya.checkPassword();
|
||||
```
|
||||
|
||||
Изменения должны касаться только выделенного фрагмента.
|
||||
|
||||
Если возможно, предложите два решения, одно -- с использованием `bind`, другое -- без него. Какое решение лучше?
|
308
01-js/06-objects-more/05-bind/article.md
Normal file
308
01-js/06-objects-more/05-bind/article.md
Normal file
|
@ -0,0 +1,308 @@
|
|||
# Привязка контекста и карринг: "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);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Параметры:
|
||||
<dl>
|
||||
<dt>`func`</dt>
|
||||
<dd>Произвольная функция</dd>
|
||||
<dt>`context`</dt>
|
||||
<dd>Произвольный объект</dd>
|
||||
|
||||
Результатом вызова `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...])
|
||||
```
|
||||
|
||||
<dl>
|
||||
<dt>`func`</dt>
|
||||
<dd>Произвольная функция</dd>
|
||||
<dt>`context`</dt>
|
||||
<dd>Обертка `wrapper` будет вызывать функцию с контекстом `this = context`.</dd>
|
||||
<dt>`arg1`, `arg2`, ...</dt>
|
||||
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
|
||||
</dl>
|
||||
|
||||
Результат вызова: `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]
|
||||
<script>
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
};
|
||||
|
||||
function ask(question, correctAnswer, ok, fail) {
|
||||
var result;
|
||||
if (typeof correctAnswer == 'boolean') {
|
||||
result = confirm(question);
|
||||
} else {
|
||||
result = prompt(question, '');
|
||||
}
|
||||
|
||||
if (result == correctAnswer) ok()
|
||||
else fail();
|
||||
}
|
||||
|
||||
function bind(func, context /*, args*/) {
|
||||
var bindArgs = [].slice.call(arguments, 2); // (1)
|
||||
function wrapper() { // (2)
|
||||
var args = [].slice.call(arguments);
|
||||
var unshiftArgs = bindArgs.concat(args); // (3)
|
||||
return func.apply(context, unshiftArgs); // (4)
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
</script>
|
||||
[/head]
|
Loading…
Add table
Add a link
Reference in a new issue