minor
This commit is contained in:
parent
d9075008f8
commit
e6e562040d
5 changed files with 71 additions and 107 deletions
|
@ -229,9 +229,7 @@ let worker = {
|
|||
worker.slow = cachingDecorator(worker.slow);
|
||||
```
|
||||
|
||||
We have two tasks to solve here.
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
|
@ -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)`.
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
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`:
|
||||
Here's a more powerful `cachingDecorator`:
|
||||
|
||||
```js run
|
||||
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);
|
||||
|
@ -356,13 +280,52 @@ alert( worker.slow(3, 5) ); // works
|
|||
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:
|
||||
|
||||
- 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]
|
||||
|
||||
|
@ -448,10 +411,9 @@ The generic *call forwarding* is usually done with `apply`:
|
|||
```js
|
||||
let wrapper = function() {
|
||||
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.
|
||||
|
||||
|
||||
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue