You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/6-objects-more/7-bind/article.md
+85-15Lines changed: 85 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,11 +73,31 @@ setTimeout(function() {
73
73
*/!*
74
74
```
75
75
76
-
Теперь код работает, так как `user` достаётся из замыкания.
76
+
Теперь код работает, так как `user` достаётся из замыкания.
77
+
78
+
Это решение также позволяет передать дополнительные аргументы:
79
+
80
+
81
+
```js
82
+
//+ run
83
+
var user = {
84
+
firstName:"Вася",
85
+
sayHi:function(who) {
86
+
alert(this.firstName+": Привет, "+ who);
87
+
}
88
+
};
89
+
90
+
*!*
91
+
setTimeout(function() {
92
+
user.sayHi("Петя"); // Вася: Привет, Петя
93
+
}, 1000);
94
+
*/!*
95
+
```
96
+
77
97
78
98
Но тут же появляется и уязвимое место в структуре кода!
79
99
80
-
**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!**
100
+
А что, если до срабатывания `setTimeout`(ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот!
81
101
82
102
Хорошо бы гарантировать правильность контекста.
83
103
@@ -87,23 +107,32 @@ setTimeout(function() {
87
107
88
108
```js
89
109
functionbind(func, context) {
90
-
returnfunction() { // (*)
110
+
returnfunction() { // (*)
91
111
returnfunc.apply(context, arguments);
92
112
};
93
113
}
94
114
```
95
115
96
-
Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно:
116
+
Посмотрим, что она делает, как работает, на таком примере:
117
+
118
+
```js
119
+
var oldSayHi =user.sayHi;
120
+
var sayHi =bind(oldSayHi, user);
121
+
```
122
+
123
+
Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
97
124
98
125
```js
99
126
function() { // (*)
100
-
returnfunc.apply(context, arguments);
127
+
returnoldSayHi.apply(user, arguments);
101
128
};
102
129
```
103
130
104
-
Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`).
131
+
Она запишется в переменную `sayHi`.
105
132
106
-
Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`.
133
+
Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`.
134
+
135
+
Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.
107
136
108
137
Пример с `bind`:
109
138
@@ -117,7 +146,7 @@ function bind(func, context) {
Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст.
159
+
Теперь всё в порядке!
131
160
161
+
Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая гарантированно вызовет `user.sayHi` в контексте `user`. В данном случае, через 1000мс.
162
+
163
+
Причём, если вызвать обёртку с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст.
164
+
165
+
```js
166
+
//+ run
167
+
var user = {
168
+
firstName:"Вася",
169
+
sayHi:function(who) {
170
+
alert(this.firstName+": Привет, "+ who);
171
+
}
172
+
};
173
+
174
+
var sayHi =bind(user.sayHi, user);
175
+
176
+
*!*
177
+
sayHi("Петя"); // Вася: Привет, Петя
178
+
sayHi("Маша"); // Вася: Привет, Маша
179
+
*/!*
180
+
```
181
+
182
+
В примере выше продемонстрирована другая частая цель использования `bind` -- "привязать" функцию к контексту, чтобы в дальнейшем "не таскать за собой" объект, а просто вызывать `sayHi`.
183
+
184
+
Результат `bind` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст.
132
185
133
186
## Решение 3: встроенный метод bind [#bind]
134
187
@@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
151
204
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
152
205
</dl>
153
206
154
-
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже.
207
+
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже.
155
208
156
209
Пример со встроенным методом `bind`:
157
210
@@ -166,13 +219,14 @@ var user = {
166
219
167
220
*!*
168
221
// setTimeout( bind(user.sayHi, user), 1000 );
169
-
170
222
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
171
223
*/!*
172
224
```
173
225
174
226
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
175
227
228
+
Далее мы будем использовать именно встроенный метод `bind`.
229
+
176
230
[smart header="Привязать всё: `bindAll`"]
177
231
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
238
292
293
+
Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`.
239
294
240
-
## Функция дла задач
295
+
## Функция ask для задач
241
296
242
297
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
243
298
@@ -251,7 +306,7 @@ function ask(question, answer, ok, fail) {
251
306
252
307
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
253
308
254
-
В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
309
+
Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
255
310
256
311
Пример использования:
257
312
@@ -272,9 +327,24 @@ function die() {
272
327
273
328
## Итого
274
329
275
-
Функции и контекст к JavaScript -- как шнурки и кроссовки. Если мы куда-то отправляем шнурки (например в `setTimeout`), то кроссовки сами за ними не побегут.
330
+
<ul>
331
+
<li>Функция сама по себе не запоминает контекст выполнения.</li>
332
+
<li>Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию:
333
+
```js
334
+
setTimeout(function() {
335
+
obj.func();
336
+
})
337
+
```
338
+
</li>
339
+
<li>...Либо использовать `bind`:
276
340
277
-
Нужно либо передать их дополнительно, либо привязать одно к другому вызовом `bind`, либо завернуть в замыкание.
341
+
```js
342
+
setTimeout( obj.func.bind(obj) );
343
+
```
344
+
</li>
345
+
<li>Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.</li>
346
+
<li>Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.</li>
Код выше не учитывает текущую прокрутку. Он присваивает `window.pageYOffset` один раз и в дальнейшем, чтобы получить текущую прокрутку, нужно снова обратиться к `document.documentElement.scrollTop` не меняет его. А задача как раз -- сделать полифилл, то есть дать возможность использовать `window.pageYOffset` для получения текущего состояния прокрутки без "танцев бубном", так же как в современных браузерах.
19
+
20
+
Для этого создадим свойство через геттер.
21
+
22
+
В IE8 для DOM-объектов работает `Object.defineProperty`:
0 commit comments