ok
This commit is contained in:
parent
1c0bebec47
commit
8bf36b7b5f
16 changed files with 639 additions and 172 deletions
|
@ -1,36 +0,0 @@
|
|||
Вызов `alert(i)` в `setTimeout` введет `100000001`.
|
||||
|
||||
Можете проверить это запуском:
|
||||
|
||||
```js run
|
||||
var timer = setInterval(function() {
|
||||
i++;
|
||||
}, 10);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(timer);
|
||||
*!*
|
||||
alert( i ); // (*)
|
||||
*/!*
|
||||
}, 50);
|
||||
|
||||
var i;
|
||||
|
||||
function f() {
|
||||
// точное время выполнения не играет роли
|
||||
// здесь оно заведомо больше 100 мс
|
||||
for (i = 0; i < 1e8; i++) f[i % 2] = i;
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
||||
Правильный вариант срабатывания: **3** (сразу же по окончании `f` один раз).
|
||||
|
||||
Планирование `setInterval` будет вызывать функцию каждые `10 мс` после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит.
|
||||
|
||||
За время выполнения `f` может пройти время, на которое запланированы несколько вызовов `setInterval`, но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы `setInterval`.
|
||||
|
||||
После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней `setInterval` и выполняет. А затем тут же выполняется `setTimeout`, очередь которого тут же подошла.
|
||||
|
||||
Итого, как раз и видим, что `setInterval` выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно.
|
|
@ -1,45 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Что выведет после setInterval?
|
||||
|
||||
В коде ниже запускается `setInterval` каждые 10 мс, и через 50 мс запланирована его отмена.
|
||||
|
||||
После этого запущена тяжёлая функция `f`, выполнение которой (мы точно знаем) потребует более 100 мс.
|
||||
|
||||
Сработает ли `setInterval`, как и когда?
|
||||
|
||||
Варианты:
|
||||
|
||||
1. Да, несколько раз, *в процессе* выполнения `f`.
|
||||
2. Да, несколько раз, *сразу после* выполнения `f`.
|
||||
3. Да, один раз, *сразу после* выполнения `f`.
|
||||
4. Нет, не сработает.
|
||||
5. Может быть по-разному, как повезёт.
|
||||
|
||||
Что выведет `alert` в строке `(*)`?
|
||||
|
||||
```js
|
||||
var i;
|
||||
var timer = setInterval(function() { // планируем setInterval каждые 10 мс
|
||||
i++;
|
||||
}, 10);
|
||||
|
||||
setTimeout(function() { // через 50 мс - отмена setInterval
|
||||
clearInterval(timer);
|
||||
*!*
|
||||
alert( i ); // (*)
|
||||
*/!*
|
||||
}, 50);
|
||||
|
||||
// и запускаем тяжёлую функцию
|
||||
function f() {
|
||||
// точное время выполнения не играет роли
|
||||
// здесь оно заведомо больше 100 мс
|
||||
for (i = 0; i < 1e8; i++) f[i % 2] = i;
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
Задача -- с небольшим "нюансом".
|
||||
|
||||
Есть браузеры, в которых на время работы JavaScript таймер "застывает", например таков IE. В них количество шагов будет почти одинаковым, +-1.
|
||||
|
||||
В других браузерах (Chrome) первый бегун будет быстрее.
|
||||
|
||||
Создадим реальные объекты `Runner` и запустим их для проверки:
|
||||
|
||||
```js run
|
||||
function Runner() {
|
||||
this.steps = 0;
|
||||
|
||||
this.step = function() {
|
||||
this.doSomethingHeavy();
|
||||
this.steps++;
|
||||
};
|
||||
|
||||
function fib(n) {
|
||||
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
this.doSomethingHeavy = function() {
|
||||
for (var i = 0; i < 25; i++) {
|
||||
this[i] = fib(i);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var runner1 = new Runner();
|
||||
var runner2 = new Runner();
|
||||
|
||||
// запускаем бегунов
|
||||
var t1 = setInterval(function() {
|
||||
runner1.step();
|
||||
}, 15);
|
||||
|
||||
var t2 = setTimeout(function go() {
|
||||
runner2.step();
|
||||
t2 = setTimeout(go, 15);
|
||||
}, 15);
|
||||
|
||||
// кто сделает больше шагов?
|
||||
setTimeout(function() {
|
||||
clearInterval(t1);
|
||||
clearTimeout(t2);
|
||||
alert( runner1.steps );
|
||||
alert( runner2.steps );
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
Если бы в шаге `step()` не было вызова `doSomethingHeavy()`, то есть он бы не требовал времени, то количество шагов было бы почти равным.
|
||||
|
||||
Но так как у нас шаг, всё же, что-то делает, и функция `doSomethingHeavy()` специально написана таким образом, что она требует (небольшого) времени, то первый бегун успеет сделать больше шагов. Ведь в `setTimeout` пауза `15` мс будет *между* шагами, а `setInterval` шагает равномерно, каждые `15` мс. Получается чаще.
|
|
@ -1,37 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Кто быстрее?
|
||||
|
||||
Есть два бегуна:
|
||||
|
||||
```js
|
||||
var runner1 = new Runner();
|
||||
var runner2 = new Runner();
|
||||
```
|
||||
|
||||
У каждого есть метод `step()`, который делает шаг, увеличивая свойство `steps`.
|
||||
|
||||
Конкретный код метода `step()` не имеет значения, важно лишь что шаг делается не мгновенно, он требует небольшого времени.
|
||||
|
||||
Если запустить первого бегуна через `setInterval`, а второго -- через вложенный `setTimeout` -- какой сделает больше шагов за 5 секунд?
|
||||
|
||||
```js
|
||||
// первый?
|
||||
setInterval(function() {
|
||||
runner1.step();
|
||||
}, 15);
|
||||
|
||||
// или второй?
|
||||
setTimeout(function go() {
|
||||
runner2.step();
|
||||
setTimeout(go, 15);
|
||||
}, 15);
|
||||
|
||||
setTimeout(function() {
|
||||
alert( runner1.steps );
|
||||
alert( runner2.steps );
|
||||
}, 5000);
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
The answer: `null`.
|
||||
|
||||
|
||||
```js run
|
||||
function f() {
|
||||
alert( this ); // null
|
||||
}
|
||||
|
||||
var user = {
|
||||
g: f.bind(null)
|
||||
};
|
||||
|
||||
user.g();
|
||||
```
|
||||
|
||||
The context of a bound function is hard-fixed. There's just no way to further change it.
|
||||
|
||||
So even while we run `user.g()`, the original function is called with `this=null`.
|
20
1-js/7-deeper/6-bind/2-write-to-object-after-bind/task.md
Normal file
20
1-js/7-deeper/6-bind/2-write-to-object-after-bind/task.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Bound function as a method
|
||||
|
||||
What will be the output?
|
||||
|
||||
```js
|
||||
function f() {
|
||||
alert( this ); // ?
|
||||
}
|
||||
|
||||
var user = {
|
||||
g: f.bind(null)
|
||||
};
|
||||
|
||||
user.g();
|
||||
```
|
||||
|
15
1-js/7-deeper/6-bind/3-second-bind/solution.md
Normal file
15
1-js/7-deeper/6-bind/3-second-bind/solution.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
The answer: **John**.
|
||||
|
||||
```js run no-beautify
|
||||
function f() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
f = f.bind( {name: "John"} ).bind( {name: "Pete"} );
|
||||
|
||||
f(); // John
|
||||
```
|
||||
|
||||
The exotic [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) object returned by `f.bind(...)` remembers the context (and arguments if provided) only at the creation time.
|
||||
|
||||
A function cannot be re-bound.
|
20
1-js/7-deeper/6-bind/3-second-bind/task.md
Normal file
20
1-js/7-deeper/6-bind/3-second-bind/task.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Second bind
|
||||
|
||||
Can we change `this` by additional binding?
|
||||
|
||||
What will be the output?
|
||||
|
||||
```js no-beautify
|
||||
function f() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
f = f.bind( {name: "John"} ).bind( {name: "Ann" } );
|
||||
|
||||
f();
|
||||
```
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
The answer: `undefined`.
|
||||
|
||||
The result of `bind` is another object. It does not have the `test` property.
|
||||
|
23
1-js/7-deeper/6-bind/4-function-property-after-bind/task.md
Normal file
23
1-js/7-deeper/6-bind/4-function-property-after-bind/task.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Function property after bind
|
||||
|
||||
There's a value in the property of a function. Will it change after `bind`? Why, elaborate?
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert( this.name );
|
||||
}
|
||||
sayHi.test = 5;
|
||||
|
||||
*!*
|
||||
let bound = sayHi.bind({
|
||||
name: "John"
|
||||
});
|
||||
|
||||
alert( bound.test ); // what will be the output? why?
|
||||
*/!*
|
||||
```
|
||||
|
49
1-js/7-deeper/6-bind/5-question-use-bind/solution.md
Normal file
49
1-js/7-deeper/6-bind/5-question-use-bind/solution.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
The error occurs because `ask` gets functions `loginOk/loginFail` without the object.
|
||||
|
||||
When it calls them, they naturally assume `this=undefined`.
|
||||
|
||||
Let's `bind` the context:
|
||||
|
||||
```js run
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result == answer) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
let user = {
|
||||
login: 'John',
|
||||
password: '12345',
|
||||
|
||||
loginOk() {
|
||||
alert(`${this.login} logged in`);
|
||||
},
|
||||
|
||||
loginFail() {
|
||||
alert(`${this.login} failed to log in`);
|
||||
},
|
||||
|
||||
checkPassword() {
|
||||
*!*
|
||||
ask("Your password?", this.password, this.loginOk.bind(this), this.loginFail.bind(this));
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
let john = user;
|
||||
user = null;
|
||||
john.checkPassword();
|
||||
```
|
||||
|
||||
Now it works.
|
||||
|
||||
An alternative solution could be:
|
||||
```js
|
||||
//...
|
||||
ask("Your password?", this.password, () => user.loginOk(), () => user.loginFail());
|
||||
```
|
||||
|
||||
...But that code would fail if `user` becomes overwritten.
|
||||
|
||||
|
49
1-js/7-deeper/6-bind/5-question-use-bind/task.md
Normal file
49
1-js/7-deeper/6-bind/5-question-use-bind/task.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Ask loosing this
|
||||
|
||||
The call to `user.checkPassword()` in the code below should `ask` for password and call `loginOk/loginFail` depending on the answer.
|
||||
|
||||
But it leads to an error. Why?
|
||||
|
||||
Fix the highlighted line for everything to start working right (other lines are not to be changed).
|
||||
|
||||
```js run
|
||||
function ask(question, answer, ok, fail) {
|
||||
let result = prompt(question, '');
|
||||
if (result == answer) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
let user = {
|
||||
login: 'John',
|
||||
password: '12345',
|
||||
|
||||
loginOk() {
|
||||
alert(`${this.login} logged in`);
|
||||
},
|
||||
|
||||
loginFail() {
|
||||
alert(`${this.login} failed to log in`);
|
||||
},
|
||||
|
||||
checkPassword() {
|
||||
*!*
|
||||
ask("Your password?", this.password, this.loginOk, this.loginFail);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
P.S. Your solution should also work if `user` gets overwritten. For instance, the last lines instead of `user.checkPassword()` would be:
|
||||
|
||||
```js
|
||||
let john = user;
|
||||
user = null;
|
||||
john.checkPassword();
|
||||
```
|
||||
|
71
1-js/7-deeper/6-bind/6-ask-currying/solution.md
Normal file
71
1-js/7-deeper/6-bind/6-ask-currying/solution.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Решение с bind
|
||||
|
||||
Первое решение -- передать в `ask` функции с привязанным контекстом и аргументами.
|
||||
|
||||
```js run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginDone: function(result) {
|
||||
alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') );
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password, this.loginDone.bind(this, true), this.loginDone.bind(this, false));
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
# Решение с локальной переменной
|
||||
|
||||
Второе решение -- это скопировать `this` в локальную переменную (чтобы внешняя перезапись не повлияла):
|
||||
|
||||
```js run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
loginDone: function(result) {
|
||||
alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') );
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
var self = this;
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() {
|
||||
self.loginDone(true);
|
||||
},
|
||||
function() {
|
||||
self.loginDone(false);
|
||||
}
|
||||
);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
user.checkPassword();
|
||||
```
|
||||
|
||||
Оба решения хороши, вариант с `bind` короче.
|
58
1-js/7-deeper/6-bind/6-ask-currying/task.md
Normal file
58
1-js/7-deeper/6-bind/6-ask-currying/task.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Использование функции вопросов с каррингом
|
||||
|
||||
Эта задача -- усложнённый вариант задачи <info:task/question-use-bind>. В ней объект `user` изменён.
|
||||
|
||||
Теперь заменим две функции `user.loginOk()` и `user.loginFail()` на единый метод: `user.loginDone(true/false)`, который нужно вызвать с `true` при верном ответе и `fail` -- при неверном.
|
||||
|
||||
Код ниже делает это, соответствующий фрагмент выделен.
|
||||
|
||||
**Сейчас он обладает важным недостатком: при записи в `user` другого значения объект перестанет корректно работать, вы увидите это, запустив пример ниже (будет ошибка).**
|
||||
|
||||
Как бы вы написали правильно?
|
||||
|
||||
**Исправьте выделенный фрагмент, чтобы код заработал.**
|
||||
|
||||
```js run
|
||||
"use strict";
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
var user = {
|
||||
login: 'Василий',
|
||||
password: '12345',
|
||||
|
||||
// метод для вызова из ask
|
||||
loginDone: function(result) {
|
||||
alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') );
|
||||
},
|
||||
|
||||
checkPassword: function() {
|
||||
*!*
|
||||
ask("Ваш пароль?", this.password,
|
||||
function() {
|
||||
user.loginDone(true);
|
||||
},
|
||||
function() {
|
||||
user.loginDone(false);
|
||||
}
|
||||
);
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
var vasya = user;
|
||||
user = null;
|
||||
vasya.checkPassword();
|
||||
```
|
||||
|
||||
Изменения должны касаться только выделенного фрагмента.
|
||||
|
||||
Если возможно, предложите два решения, одно -- с использованием `bind`, другое -- без него. Какое решение лучше?
|
291
1-js/7-deeper/6-bind/article.md
Normal file
291
1-js/7-deeper/6-bind/article.md
Normal file
|
@ -0,0 +1,291 @@
|
|||
# Binding this
|
||||
|
||||
When using `setTimeout` with object methods or passing object methods along, there's a known problem: "loosing `this`".
|
||||
|
||||
Suddenly, `this` just stops working right. The situation is typical for novice developers, but happens with experienced ones as well.
|
||||
|
||||
[cut]
|
||||
|
||||
## Loosing "this": demo
|
||||
|
||||
Let's see the problem on example.
|
||||
|
||||
Normally, `setTimeout` plans the function to execute after the delay.
|
||||
|
||||
Here it says "Hello" after 1 second:
|
||||
|
||||
```js run
|
||||
setTimeout(() => alert("Hello, John!"), 1000);
|
||||
```
|
||||
|
||||
Now let's do the same with an object method:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John",
|
||||
sayHi() {
|
||||
alert(`Hello, ${this.firstName}!`);
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
setTimeout(user.sayHi, 1000); // Hello, undefined!
|
||||
*/!*
|
||||
```
|
||||
|
||||
As we can see, the output shows not "John" as `this.firstName`, but `undefined`!
|
||||
|
||||
That's because `setTimeout` got the function `user.sayHi`, separately from the object. The last line can be rewritten as:
|
||||
|
||||
```js
|
||||
let f = user.sayHi;
|
||||
setTimeout(f, 1000); // lost user context
|
||||
```
|
||||
|
||||
The method `setTimeout` is a little special: it sets `this=window` for the function call. So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases as we'll see, usually `this` just becomes `undefined`.
|
||||
|
||||
The task is quite typical -- we want to pass an object method somewhere else where it will be called. How to make sure that it will be called in the right context?
|
||||
|
||||
There are few ways we can go.
|
||||
|
||||
## Solution 1: a wrapper
|
||||
|
||||
The simplest solution is to use an wrapping function:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John",
|
||||
sayHi() {
|
||||
alert(`Hello, ${this.firstName}!`);
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
setTimeout(function() {
|
||||
user.sayHi(); // Hello, John!
|
||||
}, 1000);
|
||||
*/!*
|
||||
```
|
||||
|
||||
Now it works, because it receives `user` from the outer lexical environment, and then calls the method normally.
|
||||
|
||||
The same function, but shorter:
|
||||
|
||||
```js
|
||||
setTimeout(() => user.sayHi(), 1000); // Hello, John!
|
||||
```
|
||||
|
||||
Looks fine, but a slight vulnerability appears in our code structure.
|
||||
|
||||
What is before `setTimeout` triggers (one second delay!) `user` will get another value. Then, suddenly, the it will call the wrong object!
|
||||
|
||||
Surely, we could write code more carefully. But it would be better if we could just guarantee the right context, no matter what.
|
||||
|
||||
## Solution 2: bind
|
||||
|
||||
There is a method [bind](mdn:js/Function/bind) that can bind `this` and arguments to a function.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```js
|
||||
// more complex syntax will be little later
|
||||
let bound = func.bind(context);
|
||||
````
|
||||
|
||||
The result of `func.bind` is a special "exotic object", that is essentially a function, but instead of having its own body, it transparently calls `func` with given arguments and `this=context`.
|
||||
|
||||
For instance, here `g` calls `func` in the context of `user`:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John"
|
||||
};
|
||||
|
||||
function func() {
|
||||
alert(this.firstName);
|
||||
}
|
||||
|
||||
*!*
|
||||
let g = func.bind(user);
|
||||
g(); // John
|
||||
*/!*
|
||||
```
|
||||
|
||||
We can think of `func.bind(user)` as a "bound" variant of `func`, with fixed `this=user`.
|
||||
|
||||
The example below demonstrates that arguments are passed "as is" to `func`:
|
||||
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John"
|
||||
};
|
||||
|
||||
function func(phrase) {
|
||||
alert(phrase + ', ' + this.firstName);
|
||||
}
|
||||
|
||||
let g = func.bind(user); // g passes all calls to func with this=user
|
||||
*!*
|
||||
g("Hello"); // Hello, John (argument "Hello" is passed as is)
|
||||
*/!*
|
||||
```
|
||||
|
||||
Now let's try with an object method:
|
||||
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John",
|
||||
sayHi() {
|
||||
alert(`Hello, ${this.firstName}!`);
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
let sayHi = user.sayHi.bind(user); // (*)
|
||||
*/!*
|
||||
|
||||
sayHi(); // Hello, John!
|
||||
|
||||
setTimeout(sayHi, 1000); // Hello, John!
|
||||
```
|
||||
|
||||
In the line `(*)` we take the method `user.sayHi` and bind it to `user`. The `sayHi` is a "bound" function, that can be called or passed to `setTimeout`.
|
||||
|
||||
It also works with arguments:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
firstName: "John",
|
||||
say(phrase) {
|
||||
alert(`${phrase}, ${this.firstName}!`);
|
||||
}
|
||||
};
|
||||
|
||||
let say = user.say.bind(user);
|
||||
|
||||
say("Hello"); // Hello, John
|
||||
say("Bye"); // Hello, John
|
||||
```
|
||||
|
||||
````smart header="Convenience method: `bindAll`"
|
||||
If an object has many methods and we plan to actively pass it around, then we could bind them all in a loop:
|
||||
|
||||
```js
|
||||
for (let key in user) {
|
||||
if (typeof user[key] == 'function') {
|
||||
user[key] = user[key].bind(user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Javascript libraries also provide functions for that, e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash.
|
||||
````
|
||||
|
||||
## Partial application
|
||||
|
||||
Till now we were only talking about binding `this`. Now let's make a step further.
|
||||
|
||||
We can bind not only `this`, but also arguments. That's more seldom used, but can also be handy.
|
||||
|
||||
The full syntax of `bind`:
|
||||
|
||||
```js
|
||||
let bound = func.bind(context, arg1, arg2, ...);
|
||||
```
|
||||
|
||||
It allows to bind context as `this` and starting arguments of the function.
|
||||
|
||||
For instance, we have a multiplication function `mul(a, b)`:
|
||||
|
||||
```js
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
```
|
||||
|
||||
Let's use `bind` to create a function, doubling the value:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let double = mul.bind(null, 2);
|
||||
*/!*
|
||||
|
||||
alert( double(3) ); // = mul(2, 3) = 6
|
||||
alert( double(4) ); // = mul(2, 4) = 8
|
||||
alert( double(5) ); // = mul(2, 5) = 10
|
||||
```
|
||||
|
||||
The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is".
|
||||
|
||||
That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one.
|
||||
|
||||
Please note that here we actually don't use the `this` context. But `bind` requires it first, so we need to pass something like `null`.
|
||||
|
||||
The function `triple` in the code below triples the value:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let triple = mul.bind(null, 3);
|
||||
*/!*
|
||||
|
||||
alert( triple(3) ); // = mul(3, 3) = 9
|
||||
alert( triple(4) ); // = mul(3, 4) = 12
|
||||
alert( triple(5) ); // = mul(3, 5) = 15
|
||||
```
|
||||
|
||||
Why to make a partial function?
|
||||
|
||||
Here our benefit is that we created an independent function with a readable name (`double`, `triple`). We can use it and don't write the first argument of every time, cause it's fixed with `bind`.
|
||||
|
||||
In other cases, partial application is useful when we have a very generic function, and want a less universal variant of it for convenience.
|
||||
|
||||
For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user.
|
||||
|
||||
## Функция ask для задач [todo]
|
||||
|
||||
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
|
||||
|
||||
```js
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
```
|
||||
|
||||
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
|
||||
|
||||
Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
ask("Выпустить птичку?", "да", fly, die);
|
||||
*/!*
|
||||
|
||||
function fly() {
|
||||
alert( 'улетела :)' );
|
||||
}
|
||||
|
||||
function die() {
|
||||
alert( 'птичку жалко :(' );
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
To safely pass an object method to `setTimeout`, we can bind the context to it.
|
||||
|
||||
The syntax:
|
||||
|
||||
```js
|
||||
let bound = func.bind(context, arg1, arg2...)
|
||||
```
|
||||
|
||||
The result is an "exotic object" that passes all calls to `func`, fixing context and arguments (if provided).
|
||||
|
||||
We can use it to fix the context (most often case), or also some of the arguments.
|
||||
|
21
1-js/7-deeper/6-bind/head.html
Normal file
21
1-js/7-deeper/6-bind/head.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
function mul(a, b) {
|
||||
return a * b;
|
||||
};
|
||||
|
||||
function ask(question, answer, ok, fail) {
|
||||
var result = prompt(question, '');
|
||||
if (result.toLowerCase() == answer.toLowerCase()) ok();
|
||||
else fail();
|
||||
}
|
||||
|
||||
function bind(func, context /*, args*/) {
|
||||
var bindArgs = [].slice.call(arguments, 2); // (1)
|
||||
function wrapper() { // (2)
|
||||
var args = [].slice.call(arguments);
|
||||
var unshiftArgs = bindArgs.concat(args); // (3)
|
||||
return func.apply(context, unshiftArgs); // (4)
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue