Skip to content

Commit 25fc5d8

Browse files
committed
renovations
1 parent 66e2f09 commit 25fc5d8

19 files changed

Lines changed: 268 additions & 88 deletions

File tree

1-js/6-objects-more/7-bind/article.md

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,31 @@ setTimeout(function() {
7373
*/!*
7474
```
7575

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+
7797

7898
Но тут же появляется и уязвимое место в структуре кода!
7999

80-
**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!**
100+
А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот!
81101

82102
Хорошо бы гарантировать правильность контекста.
83103

@@ -87,23 +107,32 @@ setTimeout(function() {
87107

88108
```js
89109
function bind(func, context) {
90-
return function() { // (*)
110+
return function() { // (*)
91111
return func.apply(context, arguments);
92112
};
93113
}
94114
```
95115

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)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
97124

98125
```js
99126
function() { // (*)
100-
return func.apply(context, arguments);
127+
return oldSayHi.apply(user, arguments);
101128
};
102129
```
103130

104-
Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`).
131+
Она запишется в переменную `sayHi`.
105132

106-
Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`.
133+
Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`.
134+
135+
Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.
107136

108137
Пример с `bind`:
109138

@@ -117,7 +146,7 @@ function bind(func, context) {
117146

118147
var user = {
119148
firstName: "Вася",
120-
sayHi: function() {
149+
sayHi: function() {
121150
alert(this.firstName);
122151
}
123152
};
@@ -127,8 +156,32 @@ setTimeout( bind(user.sayHi, user), 1000 );
127156
*/!*
128157
```
129158

130-
Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст.
159+
Теперь всё в порядке!
131160

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` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст.
132185

133186
## Решение 3: встроенный метод bind [#bind]
134187

@@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
151204
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
152205
</dl>
153206

154-
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже.
207+
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже.
155208

156209
Пример со встроенным методом `bind`:
157210

@@ -166,13 +219,14 @@ var user = {
166219

167220
*!*
168221
// setTimeout( bind(user.sayHi, user), 1000 );
169-
170222
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
171223
*/!*
172224
```
173225

174226
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
175227

228+
Далее мы будем использовать именно встроенный метод `bind`.
229+
176230
[smart header="Привязать всё: `bindAll`"]
177231
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
178232

@@ -236,8 +290,9 @@ alert( triple(5) ); // = mul(3, 5) = 15
236290

237291
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
238292

293+
Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`.
239294

240-
## Функция дла задач
295+
## Функция ask для задач
241296

242297
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
243298

@@ -251,7 +306,7 @@ function ask(question, answer, ok, fail) {
251306

252307
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
253308

254-
В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
309+
Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
255310

256311
Пример использования:
257312

@@ -272,9 +327,24 @@ function die() {
272327

273328
## Итого
274329

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`:
276340

277-
Нужно либо передать их дополнительно, либо привязать одно к другому вызовом `bind`, либо завернуть в замыкание.
341+
```js
342+
setTimeout( obj.func.bind(obj) );
343+
```
344+
</li>
345+
<li>Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.</li>
346+
<li>Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.</li>
347+
</ul>
278348

279349
[head]
280350
<script>
-5.42 KB
Loading
-5.71 KB
Loading
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
В стандартном режиме IE8 можно получить текущую прокрутку так:
2+
3+
```js
4+
//+ run
5+
alert( document.documentElement.scrollTop );
6+
```
7+
8+
Самым простым, но неверным было бы такое решение:
9+
```js
10+
//+ run
11+
// "полифилл"
12+
window.pageYOffset = document.documentElement.scrollTop;
13+
14+
// использование "полифилла"
15+
alert( window.pageYOffset );
16+
```
17+
18+
Код выше не учитывает текущую прокрутку. Он присваивает `window.pageYOffset` один раз и в дальнейшем, чтобы получить текущую прокрутку, нужно снова обратиться к `document.documentElement.scrollTop` не меняет его. А задача как раз -- сделать полифилл, то есть дать возможность использовать `window.pageYOffset` для получения текущего состояния прокрутки без "танцев бубном", так же как в современных браузерах.
19+
20+
Для этого создадим свойство через геттер.
21+
22+
В IE8 для DOM-объектов работает `Object.defineProperty`:
23+
24+
```js
25+
//+ run
26+
// полифилл
27+
Object.defineProperty(window, 'pageYOffset', {
28+
get: function() {
29+
return document.documentElement.scrollTop;
30+
}
31+
});
32+
33+
// использование полифилла
34+
alert( window.pageYOffset );
35+
```
36+
37+
38+
39+
40+
41+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Полифилл для pageYOffset в IE8
2+
3+
[importance 3]
4+
5+
Обычно в IE8 не поддерживается свойство `pageYOffset`. Напишите полифилл для него.
6+
7+
При подключённом полифилле такой код должен работать в IE8:
8+
9+
```js
10+
// текущая прокрутка страницы в IE8
11+
alert( window.pageYOffset );
12+
```

2-ui/1-document/18-coordinates-document/article.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ function getOffsetSum(elem) {
107107
var top = 0, left = 0;
108108

109109
while(elem) {
110-
top = top + parseInt(elem.offsetTop);
111-
left = left + parseInt(elem.offsetLeft);
112-
elem = elem.offsetParent;
110+
top = top + parseInt(elem.offsetTop);
111+
left = left + parseInt(elem.offsetLeft);
112+
elem = elem.offsetParent;
113113
}
114114

115115
return {top: top, left: left};

0 commit comments

Comments
 (0)