renovations
This commit is contained in:
parent
66e2f0919d
commit
25fc5d8650
19 changed files with 268 additions and 88 deletions
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue