renovations

This commit is contained in:
Ilya Kantor 2015-02-19 23:57:02 +03:00
parent e706693c7e
commit 24171550ae
23 changed files with 196 additions and 76 deletions

View file

@ -7,7 +7,7 @@ function f() {
}
var user = {
g: bind(f, "Hello")
g: f.bind("Hello")
}
user.g();

View file

@ -10,7 +10,7 @@ function f() {
}
var user = {
g: bind(f, "Hello")
g: f.bind("Hello")
}
user.g();

View file

@ -116,25 +116,57 @@ function bind(func, context) {
Посмотрим, что она делает, как работает, на таком примере:
```js
var oldSayHi = user.sayHi;
var sayHi = bind(oldSayHi, user);
//+ run
function f() { alert(this); }
var g = bind(f, "Context");
g(); // Context
```
Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
То есть, `bind(f, "Context")` привязывает `"Context"` в качестве `this` для `f`.
Посмотрим, за счёт чего это происходит.
Результатом `bind(f, "Context")`, как видно из кода, будет анонимная функция `(*)`.
Вот она отдельно:
```js
function() { // (*)
return oldSayHi.apply(user, arguments);
return func.apply(context, arguments);
};
```
Она запишется в переменную `sayHi`.
Если подставить наши конкретные аргументы, то есть `f` и `"Context"`, то получится так:
Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`.
```js
function() { // (*)
return f.apply("Context", arguments);
};
```
Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.
Эта функция запишется в переменную `g`.
Пример с `bind`:
Далее, если вызвать `g`, то вызов будет передан в `f`, причём `f.apply("Context", arguments)` передаст в качестве контекста `"Context"`, который и будет выведен.
Если вызвать `g` с аргументами, то также будет работать:
```js
//+ run
function f(a, b) {
alert(this);
alert(a + b);
}
var g = bind(f, "Context");
g(1, 2); // Context, затем 3
```
Аргументы, которые получила `g(...)`, передаются в `f` также благодаря методу `.apply`.
**Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.**
Вернёмся к `user.sayHi`. Вариант с `bind`:
```js
//+ run
@ -158,15 +190,17 @@ setTimeout( bind(user.sayHi, user), 1000 );
Теперь всё в порядке!
Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая гарантированно вызовет `user.sayHi` в контексте `user`. В данном случае, через 1000мс.
Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая привязывает вызовет `user.sayHi` к контексту `user`. Она будет вызвана через 1000мс.
Причём, если вызвать обёртку с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст.
Полученную обёртку можно вызвать и с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст.
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function(who) {
*!*
sayHi: function(who) { // здесь у sayHi есть один аргумент
*/!*
alert(this.firstName + ": Привет, " + who);
}
};
@ -174,6 +208,7 @@ var user = {
var sayHi = bind(user.sayHi, user);
*!*
// контекст Вася, а аргумент передаётся "как есть"
sayHi("Петя"); // Вася: Привет, Петя
sayHi("Маша"); // Вася: Привет, Маша
*/!*
@ -187,9 +222,26 @@ sayHi("Маша"); // Вася: Привет, Маша
В современном JavaScript (или при подключении библиотеки [es5-shim](https://github.com/kriskowal/es5-shim) для IE8-) у функций уже есть встроенный метод [bind](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind), который мы можем использовать.
Он позволяет получить обёртку, которая привязывает функцию не только к нужному контексту но, если нужно, то и к аргументам.
Он работает примерно так же, как `bind`, который описан выше.
Синтаксис `bind`:
Изменения очень небольшие:
```js
//+ run
function f(a, b) {
alert(this);
alert(a + b);
}
*!*
// вместо
// var g = bind(f, "Context");
var g = f.bind("Context");
*/!*
g(1, 2); // Context, затем 3
```
Синтаксис встроенного `bind`:
```js
var wrapper = func.bind(context[, arg1, arg2...])
@ -199,7 +251,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
<dt>`func`</dt>
<dd>Произвольная функция</dd>
<dt>`context`</dt>
<dd>Обертка `wrapper` будет вызывать функцию с контекстом `this = context`.</dd>
<dd>Контекст, который привязывается к `func`</dd>
<dt>`arg1`, `arg2`, ...</dt>
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
</dl>
@ -227,6 +279,14 @@ setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встро
Далее мы будем использовать именно встроенный метод `bind`.
[warn header="bind не похож call/apply"]
Методы `bind` и `call/apply` близки по синтаксису, но есть важнейшее отличие.
Методы `call/apply` вызывают функцию с заданным контекстом и аргументами.
А `bind` не вызывает функцию. Он только возвращает "обёртку", которую мы можем вызвать позже, и которая передаст вызов в исходную функцию, с привязанным контекстом.
[/warn]
[smart header="Привязать всё: `bindAll`"]
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
@ -241,6 +301,7 @@ for(var prop in user) {
В некоторых JS-фреймворках есть даже встроенные функции для этого, например [_.bindAll(obj)](http://lodash.com/docs#bindAll).
[/smart]
## Карринг
До этого мы говорили о привязке контекста. Теперь пойдём на шаг дальше. Привязывать можно не только контекст, но и аргументы. Используется это реже, но бывает полезно.