Skip to content

Commit 878d1cb

Browse files
authored
Merge pull request #261 from odsantos/update-decorators-and-forwarding
Update "Decorators and forwarding" files
2 parents 1985197 + c77e665 commit 878d1cb

File tree

2 files changed

+19
-25
lines changed

2 files changed

+19
-25
lines changed

1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
88

99
When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
1010

11-
The difference with debounce is that it's completely different decorator:
11+
Compared to the debounce decorator, the behavior is completely different:
1212
- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
1313
- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
1414

@@ -21,16 +21,16 @@ Let's check the real-life application to better understand that requirement and
2121
In a 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).
2222
**We'd like to update some information on the web-page when the pointer moves.**
2323

24-
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.
24+
...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.
2525

26-
So we'll assign `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.
26+
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.
2727

2828
Visually, it will look like this:
2929

30-
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.
30+
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.
3131
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
32-
3. At the end of `100ms` -- one more `update` happens with the last coordinates.
33-
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.
32+
3. At the end of `100ms` -- one more `update` happens with the last coordinates.
33+
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.
3434

3535
A code example:
3636

1-js/06-advanced-functions/09-call-apply-decorators/article.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ JavaScript gives exceptional flexibility when dealing with functions. They can b
66

77
Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result.
88

9-
If the function is called often, we may want to cache (remember) the results for different `x` to avoid spending extra-time on recalculations.
9+
If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.
1010

11-
But instead of adding that functionality into `slow()` we'll create a wrapper. As we'll see, there are many benefits of doing so.
11+
But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so.
1212

1313
Here's the code, and explanations follow:
1414

@@ -23,13 +23,13 @@ function cachingDecorator(func) {
2323
let cache = new Map();
2424

2525
return function(x) {
26-
if (cache.has(x)) { // if the result is in the map
27-
return cache.get(x); // return it
26+
if (cache.has(x)) { // if there's such key in cache
27+
return cache.get(x); // read the result from it
2828
}
2929

30-
let result = func(x); // otherwise call func
30+
let result = func(x); // otherwise call func
3131

32-
cache.set(x, result); // and cache (remember) the result
32+
cache.set(x, result); // and cache (remember) the result
3333
return result;
3434
};
3535
}
@@ -49,13 +49,11 @@ The idea is that we can call `cachingDecorator` for any function, and it will re
4949

5050
By separating caching from the main function code we also keep the main code simpler.
5151

52-
Now let's get into details of how it works.
53-
5452
The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic:
5553

5654
![](decorator-makecaching-wrapper.svg)
5755

58-
As we can see, the wrapper returns the result of `func(x)` "as is". From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
56+
From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
5957

6058
To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself:
6159

@@ -228,22 +226,19 @@ let worker = {
228226
worker.slow = cachingDecorator(worker.slow);
229227
```
230228

231-
We have two tasks to solve here.
232-
233-
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.
229+
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.
234230

235231
There are many solutions possible:
236232

237233
1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys.
238234
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)`.
239235
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.
240236

241-
242237
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
243238

244239
Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`.
245240

246-
Now let's bake it all into the more powerful `cachingDecorator`:
241+
Here's a more powerful `cachingDecorator`:
247242

248243
```js run
249244
let worker = {
@@ -264,7 +259,7 @@ function cachingDecorator(func, hash) {
264259
}
265260

266261
*!*
267-
let result = func.apply(this, arguments); // (**)
262+
let result = func.call(this, ...arguments); // (**)
268263
*/!*
269264

270265
cache.set(key, result);
@@ -287,7 +282,7 @@ Now it works with any number of arguments (though the hash function would also n
287282
There are two changes:
288283

289284
- 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.
290-
- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function.
285+
- 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.
291286

292287
## func.apply
293288

@@ -423,10 +418,9 @@ The generic *call forwarding* is usually done with `apply`:
423418
```js
424419
let wrapper = function() {
425420
return original.apply(this, arguments);
426-
}
421+
};
427422
```
428423

429-
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.
430-
424+
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.
431425

432426
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.

0 commit comments

Comments
 (0)