minor
This commit is contained in:
parent
d9075008f8
commit
e6e562040d
5 changed files with 71 additions and 107 deletions
|
@ -14,18 +14,18 @@ Let's check the real-life application to better understand that requirement and
|
||||||
|
|
||||||
In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
|
In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
|
||||||
|
|
||||||
**The tracking function should update some information on the web-page.**
|
**We'd like to update some information on the web-page when the pointer moves.**
|
||||||
|
|
||||||
Updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in making it more often than once per 100ms.
|
...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms.
|
||||||
|
|
||||||
So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but `update()` will be called at maximum once per 100ms.
|
So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms.
|
||||||
|
|
||||||
Visually, it will look like this:
|
Visually, it will look like this:
|
||||||
|
|
||||||
1. For the first mouse movement the decorated variant passes the call to `update`. That's important, the user sees our reaction to their move immediately.
|
1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately.
|
||||||
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
|
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
|
||||||
3. At the end of `100ms` -- one more `update` happens with the last coordinates.
|
3. At the end of `100ms` -- one more `update` happens with the last coordinates.
|
||||||
4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, perhaps the most important, the final mouse coordinates are processed.
|
4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed.
|
||||||
|
|
||||||
A code example:
|
A code example:
|
||||||
|
|
||||||
|
|
|
@ -229,9 +229,7 @@ let worker = {
|
||||||
worker.slow = cachingDecorator(worker.slow);
|
worker.slow = cachingDecorator(worker.slow);
|
||||||
```
|
```
|
||||||
|
|
||||||
We have two tasks to solve here.
|
Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
|
||||||
|
|
||||||
First is how to use both arguments `min` and `max` for the key in `cache` map. Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
|
|
||||||
|
|
||||||
There are many solutions possible:
|
There are many solutions possible:
|
||||||
|
|
||||||
|
@ -239,85 +237,11 @@ There are many solutions possible:
|
||||||
2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`.
|
2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`.
|
||||||
3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many.
|
3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many.
|
||||||
|
|
||||||
|
|
||||||
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
|
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
|
||||||
|
|
||||||
The second task to solve is how to pass many arguments to `func`. Currently, the wrapper `function(x)` assumes a single argument, and `func.call(this, x)` passes it.
|
Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one.
|
||||||
|
|
||||||
Here we can use another built-in method [func.apply](mdn:js/Function/apply).
|
Here's a more powerful `cachingDecorator`:
|
||||||
|
|
||||||
The syntax is:
|
|
||||||
|
|
||||||
```js
|
|
||||||
func.apply(context, args)
|
|
||||||
```
|
|
||||||
|
|
||||||
It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments.
|
|
||||||
|
|
||||||
|
|
||||||
For instance, these two calls are almost the same:
|
|
||||||
|
|
||||||
```js
|
|
||||||
func(1, 2, 3);
|
|
||||||
func.apply(context, [1, 2, 3])
|
|
||||||
```
|
|
||||||
|
|
||||||
Both run `func` giving it arguments `1,2,3`. But `apply` also sets `this=context`.
|
|
||||||
|
|
||||||
For instance, here `say` is called with `this=user` and `messageData` as a list of arguments:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function say(time, phrase) {
|
|
||||||
alert(`[${time}] ${this.name}: ${phrase}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = { name: "John" };
|
|
||||||
|
|
||||||
let messageData = ['10:00', 'Hello']; // become time and phrase
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// user becomes this, messageData is passed as a list of arguments (time, phrase)
|
|
||||||
say.apply(user, messageData); // [10:00] John: Hello (this=user)
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them.
|
|
||||||
|
|
||||||
We already know the spread operator `...` from the chapter <info:rest-parameters-spread-operator> that can pass an array (or any iterable) as a list of arguments. So if we use it with `call`, we can achieve almost the same as `apply`.
|
|
||||||
|
|
||||||
These two calls are almost equivalent:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let args = [1, 2, 3];
|
|
||||||
|
|
||||||
*!*
|
|
||||||
func.call(context, ...args); // pass an array as list with spread operator
|
|
||||||
func.apply(context, args); // is same as using apply
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
If we look more closely, there's a minor difference between such uses of `call` and `apply`.
|
|
||||||
|
|
||||||
- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
|
|
||||||
- The `apply` accepts only *array-like* `args`.
|
|
||||||
|
|
||||||
So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
|
|
||||||
|
|
||||||
And if `args` is both iterable and array-like, like a real array, then we technically could use any of them, but `apply` will probably be faster, because it's a single operation. Most JavaScript engines internally optimize it better than a pair `call + spread`.
|
|
||||||
|
|
||||||
One of the most important uses of `apply` is passing the call to another function, like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let wrapper = function() {
|
|
||||||
return anotherFunction.apply(this, arguments);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
That's called *call forwarding*. The `wrapper` passes everything it gets: the context `this` and arguments to `anotherFunction` and returns back its result.
|
|
||||||
|
|
||||||
When an external code calls such `wrapper`, it is indistinguishable from the call of the original function.
|
|
||||||
|
|
||||||
Now let's bake it all into the more powerful `cachingDecorator`:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let worker = {
|
let worker = {
|
||||||
|
@ -338,7 +262,7 @@ function cachingDecorator(func, hash) {
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
let result = func.apply(this, arguments); // (**)
|
let result = func.call(this, ...arguments); // (**)
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
cache.set(key, result);
|
cache.set(key, result);
|
||||||
|
@ -356,13 +280,52 @@ alert( worker.slow(3, 5) ); // works
|
||||||
alert( "Again " + worker.slow(3, 5) ); // same (cached)
|
alert( "Again " + worker.slow(3, 5) ); // same (cached)
|
||||||
```
|
```
|
||||||
|
|
||||||
Now the wrapper operates with any number of arguments.
|
Now it works with any number of arguments.
|
||||||
|
|
||||||
There are two changes:
|
There are two changes:
|
||||||
|
|
||||||
- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions.
|
- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions.
|
||||||
- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function.
|
- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function.
|
||||||
|
|
||||||
|
Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`.
|
||||||
|
|
||||||
|
The syntax of built-in method [func.apply](mdn:js/Function/apply) is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
func.apply(context, args)
|
||||||
|
```
|
||||||
|
|
||||||
|
It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments.
|
||||||
|
|
||||||
|
The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them.
|
||||||
|
|
||||||
|
So these two calls are almost equivalent:
|
||||||
|
|
||||||
|
```js
|
||||||
|
func.call(context, ...args); // pass an array as list with spread operator
|
||||||
|
func.apply(context, args); // is same as using apply
|
||||||
|
```
|
||||||
|
|
||||||
|
There's only a minor difference:
|
||||||
|
|
||||||
|
- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
|
||||||
|
- The `apply` accepts only *array-like* `args`.
|
||||||
|
|
||||||
|
So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
|
||||||
|
|
||||||
|
And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
|
||||||
|
|
||||||
|
Passing all arguments along with the context to another function is called *call forwarding*.
|
||||||
|
|
||||||
|
That's the simplest form of it:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let wrapper = function() {
|
||||||
|
return func.apply(this, arguments);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`.
|
||||||
|
|
||||||
## Borrowing a method [#method-borrowing]
|
## Borrowing a method [#method-borrowing]
|
||||||
|
|
||||||
|
@ -448,10 +411,9 @@ The generic *call forwarding* is usually done with `apply`:
|
||||||
```js
|
```js
|
||||||
let wrapper = function() {
|
let wrapper = function() {
|
||||||
return original.apply(this, arguments);
|
return original.apply(this, arguments);
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array.
|
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array.
|
||||||
|
|
||||||
|
|
||||||
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
|
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
|
||||||
|
|
|
@ -5,13 +5,13 @@ libs:
|
||||||
|
|
||||||
# Function binding
|
# Function binding
|
||||||
|
|
||||||
When using `setTimeout` with object methods or passing object methods along, there's a known problem: "losing `this`".
|
When passing object methods as callbacks, for instance to `setTimeout`, there's a known problem: "losing `this`".
|
||||||
|
|
||||||
Suddenly, `this` just stops working right. The situation is typical for novice developers, but happens with experienced ones as well.
|
In this chapter we'll see the ways to fix it.
|
||||||
|
|
||||||
## Losing "this"
|
## Losing "this"
|
||||||
|
|
||||||
We already know that in JavaScript it's easy to lose `this`. Once a method is passed somewhere separately from the object -- `this` is lost.
|
We've already seen examples of losing `this`. Once a method is passed somewhere separately from the object -- `this` is lost.
|
||||||
|
|
||||||
Here's how it may happen with `setTimeout`:
|
Here's how it may happen with `setTimeout`:
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ let f = user.sayHi;
|
||||||
setTimeout(f, 1000); // lost user context
|
setTimeout(f, 1000); // lost user context
|
||||||
```
|
```
|
||||||
|
|
||||||
The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). 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 method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases, usually `this` just becomes `undefined`.
|
||||||
|
|
||||||
The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context?
|
The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context?
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ async function f() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The word "async" before a function means one simple thing: a function always returns a promise. Even If a function actually returns a non-promise value, prepending the function definition with the "async" keyword directs JavaScript to automatically wrap that value in a resolved promise.
|
The word "async" before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.
|
||||||
|
|
||||||
For instance, the code above returns a resolved promise with the result of `1`, let's test it:
|
For instance, this function returns a resolved promise with the result of `1`, let's test it:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
async function f() {
|
async function f() {
|
||||||
|
@ -24,7 +24,7 @@ async function f() {
|
||||||
f().then(alert); // 1
|
f().then(alert); // 1
|
||||||
```
|
```
|
||||||
|
|
||||||
...We could explicitly return a promise, that would be the same as:
|
...We could explicitly return a promise, that would be the same:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
async function f() {
|
async function f() {
|
||||||
|
@ -171,7 +171,7 @@ f();
|
||||||
If `await` gets a non-promise object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
|
If `await` gets a non-promise object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
|
||||||
````
|
````
|
||||||
|
|
||||||
````smart header="Async methods"
|
````smart header="Async class methods"
|
||||||
To declare an async class method, just prepend it with `async`:
|
To declare an async class method, just prepend it with `async`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
@ -214,7 +214,7 @@ async function f() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In real situations, the promise may take some time before it rejects. So `await` will wait, and then throw an error.
|
In real situations, the promise may take some time before it rejects. In that case there will be a delay before `await` throws an error.
|
||||||
|
|
||||||
We can catch that error using `try..catch`, the same way as a regular `throw`:
|
We can catch that error using `try..catch`, the same way as a regular `throw`:
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
The JavaScript language was initially created for web browsers. Since then, it has evolved and become a language with many uses and platforms.
|
The JavaScript language was initially created for web browsers. Since then, it has evolved and become a language with many uses and platforms.
|
||||||
|
|
||||||
A platform may be a browser, or a web-server, or a washing machine, or another *host*. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
|
A platform may be a browser, or a web-server or another *host*, even a coffee machine. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
|
||||||
|
|
||||||
A host environment provides platform-specific objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.
|
A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.
|
||||||
|
|
||||||
Here's a bird's-eye view of what we have when JavaScript runs in a web-browser:
|
Here's a bird's-eye view of what we have when JavaScript runs in a web-browser:
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@ There are more window-specific methods and properties, we'll cover them later.
|
||||||
|
|
||||||
## DOM (Document Object Model)
|
## DOM (Document Object Model)
|
||||||
|
|
||||||
The `document` object gives access to the page content. We can change or create anything on the page using it.
|
Document Object Model, or DOM for short, represents all page content as objects that can be modified.
|
||||||
|
|
||||||
|
The `document` object is the main "entry point" to the page. We can change or create anything on the page using it.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
```js run
|
```js run
|
||||||
|
@ -52,15 +54,15 @@ Here we used `document.body.style`, but there's much, much more. Properties and
|
||||||
- **DOM Living Standard** at <https://dom.spec.whatwg.org>
|
- **DOM Living Standard** at <https://dom.spec.whatwg.org>
|
||||||
|
|
||||||
```smart header="DOM is not only for browsers"
|
```smart header="DOM is not only for browsers"
|
||||||
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too.
|
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too.
|
||||||
|
|
||||||
For instance, server-side tools that download HTML pages and process them use the DOM. They may support only a part of the specification though.
|
For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though.
|
||||||
```
|
```
|
||||||
|
|
||||||
```smart header="CSSOM for styling"
|
```smart header="CSSOM for styling"
|
||||||
CSS rules and stylesheets are not structured like HTML. There's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how they are represented as objects, and how to read and write them.
|
CSS rules and stylesheets are structured in a different way than HTML. There's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how they are represented as objects, and how to read and write them.
|
||||||
|
|
||||||
CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, so we won't cover it right now.
|
CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, but that's also possible.
|
||||||
```
|
```
|
||||||
|
|
||||||
## BOM (Browser object model)
|
## BOM (Browser object model)
|
||||||
|
@ -76,7 +78,7 @@ Here's how we can use the `location` object:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
alert(location.href); // shows current URL
|
alert(location.href); // shows current URL
|
||||||
if (confirm("Go to wikipedia?")) {
|
if (confirm("Go to Wikipedia?")) {
|
||||||
location.href = "https://wikipedia.org"; // redirect the browser to another URL
|
location.href = "https://wikipedia.org"; // redirect the browser to another URL
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue