renovations

This commit is contained in:
Ilya Kantor 2015-02-07 12:34:26 +03:00
parent 66e2f0919d
commit 25fc5d8650
19 changed files with 268 additions and 88 deletions

View file

@ -73,11 +73,31 @@ setTimeout(function() {
*/!*
```
Теперь код работает, так как `user` достаётся из замыкания.
Теперь код работает, так как `user` достаётся из замыкания.
Это решение также позволяет передать дополнительные аргументы:
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function(who) {
alert(this.firstName + ": Привет, " + who);
}
};
*!*
setTimeout(function() {
user.sayHi("Петя"); // Вася: Привет, Петя
}, 1000);
*/!*
```
Но тут же появляется и уязвимое место в структуре кода!
**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!**
А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот!
Хорошо бы гарантировать правильность контекста.
@ -87,23 +107,32 @@ setTimeout(function() {
```js
function bind(func, context) {
return function() { // (*)
return function() { // (*)
return func.apply(context, arguments);
};
}
```
Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно:
Посмотрим, что она делает, как работает, на таком примере:
```js
var oldSayHi = user.sayHi;
var sayHi = bind(oldSayHi, user);
```
Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
```js
function() { // (*)
return func.apply(context, arguments);
return oldSayHi.apply(user, arguments);
};
```
Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`).
Она запишется в переменную `sayHi`.
Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`.
Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`.
Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.
Пример с `bind`:
@ -117,7 +146,7 @@ function bind(func, context) {
var user = {
firstName: "Вася",
sayHi: function() {
sayHi: function() {
alert(this.firstName);
}
};
@ -127,8 +156,32 @@ setTimeout( bind(user.sayHi, user), 1000 );
*/!*
```
Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст.
Теперь всё в порядке!
Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая гарантированно вызовет `user.sayHi` в контексте `user`. В данном случае, через 1000мс.
Причём, если вызвать обёртку с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст.
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function(who) {
alert(this.firstName + ": Привет, " + who);
}
};
var sayHi = bind(user.sayHi, user);
*!*
sayHi("Петя"); // Вася: Привет, Петя
sayHi("Маша"); // Вася: Привет, Маша
*/!*
```
В примере выше продемонстрирована другая частая цель использования `bind` -- "привязать" функцию к контексту, чтобы в дальнейшем "не таскать за собой" объект, а просто вызывать `sayHi`.
Результат `bind` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст.
## Решение 3: встроенный метод bind [#bind]
@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
</dl>
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже.
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже.
Пример со встроенным методом `bind`:
@ -166,13 +219,14 @@ var user = {
*!*
// setTimeout( bind(user.sayHi, user), 1000 );
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
*/!*
```
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
Далее мы будем использовать именно встроенный метод `bind`.
[smart header="Привязать всё: `bindAll`"]
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
@ -236,8 +290,9 @@ alert( triple(5) ); // = mul(3, 5) = 15
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`.
## Функция дла задач
## Функция ask для задач
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
@ -251,7 +306,7 @@ function ask(question, answer, ok, fail) {
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
Пример использования:
@ -272,9 +327,24 @@ function die() {
## Итого
Функции и контекст к JavaScript -- как шнурки и кроссовки. Если мы куда-то отправляем шнурки (например в `setTimeout`), то кроссовки сами за ними не побегут.
<ul>
<li>Функция сама по себе не запоминает контекст выполнения.</li>
<li>Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию:
```js
setTimeout(function() {
obj.func();
})
```
</li>
<li>...Либо использовать `bind`:
Нужно либо передать их дополнительно, либо привязать одно к другому вызовом `bind`, либо завернуть в замыкание.
```js
setTimeout( obj.func.bind(obj) );
```
</li>
<li>Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.</li>
<li>Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.</li>
</ul>
[head]
<script>