You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
8
8
9
9
When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
10
10
11
-
The difference with debounce is that it's completely different decorator:
11
+
Compared to the debounce decorator, the behavior is completely different:
12
12
-`debounce` runs the function once after the "cooldown" period. Good for processing the final result.
13
13
-`throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
14
14
@@ -21,16 +21,16 @@ Let's check the real-life application to better understand that requirement and
21
21
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).
22
22
**We'd like to update some information on the web-page when the pointer moves.**
23
23
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.
25
25
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.
27
27
28
28
Visually, it will look like this:
29
29
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.
31
31
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.
Copy file name to clipboardExpand all lines: 1-js/06-advanced-functions/09-call-apply-decorators/article.md
+13-19Lines changed: 13 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,9 +6,9 @@ JavaScript gives exceptional flexibility when dealing with functions. They can b
6
6
7
7
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.
8
8
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.
10
10
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.
12
12
13
13
Here's the code, and explanations follow:
14
14
@@ -23,13 +23,13 @@ function cachingDecorator(func) {
23
23
let cache =newMap();
24
24
25
25
returnfunction(x) {
26
-
if (cache.has(x)) { // if the result is in the map
27
-
returncache.get(x); //return it
26
+
if (cache.has(x)) { // if there's such key in cache
27
+
returncache.get(x); //read the result from it
28
28
}
29
29
30
-
let result =func(x); // otherwise call func
30
+
let result =func(x); // otherwise call func
31
31
32
-
cache.set(x, result); // and cache (remember) the result
32
+
cache.set(x, result); // and cache (remember) the result
33
33
return result;
34
34
};
35
35
}
@@ -49,13 +49,11 @@ The idea is that we can call `cachingDecorator` for any function, and it will re
49
49
50
50
By separating caching from the main function code we also keep the main code simpler.
51
51
52
-
Now let's get into details of how it works.
53
-
54
52
The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic:
55
53
56
54

57
55
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.
59
57
60
58
To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself:
61
59
@@ -228,22 +226,19 @@ let worker = {
228
226
worker.slow=cachingDecorator(worker.slow);
229
227
```
230
228
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.
234
230
235
231
There are many solutions possible:
236
232
237
233
1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys.
238
234
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)`.
239
235
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.
240
236
241
-
242
237
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
243
238
244
239
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)`.
245
240
246
-
Now let's bake it all into the more powerful `cachingDecorator`:
241
+
Here's a more powerful `cachingDecorator`:
247
242
248
243
```js run
249
244
let worker = {
@@ -264,7 +259,7 @@ function cachingDecorator(func, hash) {
264
259
}
265
260
266
261
*!*
267
-
let result =func.apply(this, arguments); // (**)
262
+
let result =func.call(this, ...arguments); // (**)
268
263
*/!*
269
264
270
265
cache.set(key, result);
@@ -287,7 +282,7 @@ Now it works with any number of arguments (though the hash function would also n
287
282
There are two changes:
288
283
289
284
- 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.
291
286
292
287
## func.apply
293
288
@@ -423,10 +418,9 @@ The generic *call forwarding* is usually done with `apply`:
423
418
```js
424
419
letwrapper=function() {
425
420
returnoriginal.apply(this, arguments);
426
-
}
421
+
};
427
422
```
428
423
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.
431
425
432
426
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
0 commit comments