minor
This commit is contained in:
parent
ff09b8c496
commit
6782931a9e
2 changed files with 19 additions and 19 deletions
|
@ -1,7 +1,5 @@
|
||||||
# WeakMap and WeakSet
|
# WeakMap and WeakSet
|
||||||
|
|
||||||
`WeakSet` is a special kind of `Set` that does not prevent JavaScript from removing its items from memory. `WeakMap` is the same thing for `Map`.
|
|
||||||
|
|
||||||
As we know from the chapter <info:garbage-collection>, JavaScript engine stores a value in memory while it is reachable (and can potentially be used).
|
As we know from the chapter <info:garbage-collection>, JavaScript engine stores a value in memory while it is reachable (and can potentially be used).
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
@ -55,9 +53,9 @@ john = null; // overwrite the reference
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
`WeakMap/WeakSet` are fundamentally different in this aspect. They do not prevent garbage-collection of key objects.
|
`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects.
|
||||||
|
|
||||||
Let's explain it starting with `WeakMap`.
|
Let's see what it means on examples.
|
||||||
|
|
||||||
## WeakMap
|
## WeakMap
|
||||||
|
|
||||||
|
@ -110,7 +108,7 @@ Now where do we need such data structure?
|
||||||
|
|
||||||
The main area of application for `WeakMap` is an *additional data storage*.
|
The main area of application for `WeakMap` is an *additional data storage*.
|
||||||
|
|
||||||
If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is the right choice!
|
If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed.
|
||||||
|
|
||||||
We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well.
|
We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well.
|
||||||
|
|
||||||
|
@ -149,7 +147,7 @@ countUser(john);
|
||||||
john = null;
|
john = null;
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we have a problem: `john` object should be garbage collected, but remains is memory, as it's a key in `visitsCountMap`.
|
Now `john` object should be garbage collected, but remains is memory, as it's a key in `visitsCountMap`.
|
||||||
|
|
||||||
We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures.
|
We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures.
|
||||||
|
|
||||||
|
@ -166,13 +164,13 @@ function countUser(user) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we don't have to clean `visitsCountMap`. After `john` is removed from memory, the additionally stored information from `WeakMap` will be removed as well.
|
Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`.
|
||||||
|
|
||||||
## Use case: caching
|
## Use case: caching
|
||||||
|
|
||||||
Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it.
|
Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it.
|
||||||
|
|
||||||
We can use `Map` for it, like this:
|
We can use `Map` to store results, like this:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// 📁 cache.js
|
// 📁 cache.js
|
||||||
|
@ -181,7 +179,7 @@ let cache = new Map();
|
||||||
// calculate and remember the result
|
// calculate and remember the result
|
||||||
function process(obj) {
|
function process(obj) {
|
||||||
if (!cache.has(obj)) {
|
if (!cache.has(obj)) {
|
||||||
let result = /* calculate the result for */ obj;
|
let result = /* calculations of the result for */ obj;
|
||||||
|
|
||||||
cache.set(obj, result);
|
cache.set(obj, result);
|
||||||
}
|
}
|
||||||
|
@ -190,25 +188,26 @@ function process(obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
// Usage in another file:
|
// Now we use process() in another file:
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
// 📁 main.js
|
// 📁 main.js
|
||||||
let obj = {/* some object */};
|
let obj = {/* let's say we have an object */};
|
||||||
|
|
||||||
let result1 = process(obj); // calculated
|
let result1 = process(obj); // calculated
|
||||||
|
|
||||||
// ...later, from another place of the code...
|
// ...later, from another place of the code...
|
||||||
let result2 = process(obj); // taken from cache
|
let result2 = process(obj); // remembered result taken from cache
|
||||||
|
|
||||||
// ...later, when the object is not needed any more:
|
// ...later, when the object is not needed any more:
|
||||||
obj = null;
|
obj = null;
|
||||||
|
|
||||||
alert(cache.size); // 1 (Ouch! It's still in cache, taking memory!)
|
alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)
|
||||||
```
|
```
|
||||||
|
|
||||||
Now for multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more.
|
For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more.
|
||||||
|
|
||||||
If we replace `Map` with `WeakMap`, then the cached result will be removed from memory automatically after the object gets garbage collected:
|
If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected.
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// 📁 cache.js
|
// 📁 cache.js
|
||||||
|
@ -236,7 +235,8 @@ let result2 = process(obj);
|
||||||
// ...later, when the object is not needed any more:
|
// ...later, when the object is not needed any more:
|
||||||
obj = null;
|
obj = null;
|
||||||
|
|
||||||
// Can't get cache.size, as it's a WeakMap, but it's 0 or soon be 0
|
// Can't get cache.size, as it's a WeakMap,
|
||||||
|
// but it's 0 or soon be 0
|
||||||
// When obj gets garbage collected, cached data will be removed as well
|
// When obj gets garbage collected, cached data will be removed as well
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ obj = null;
|
||||||
|
|
||||||
Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object.
|
Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object.
|
||||||
|
|
||||||
For instance, we can use `WeakSet` to keep track of users that visited our site:
|
For instance, we can add users to `WeakSet` to keep track of those who visited our site:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let visitedSet = new WeakSet();
|
let visitedSet = new WeakSet();
|
||||||
|
|
|
@ -241,7 +241,7 @@ There's a special use case: `setTimeout(func, 0)`, or just `setTimeout(func)`.
|
||||||
|
|
||||||
This schedules the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete.
|
This schedules the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete.
|
||||||
|
|
||||||
So the function is scheduled to run "right after" the current code. In other words, *asynchronously*.
|
So the function is scheduled to run "right after" the current code.
|
||||||
|
|
||||||
For instance, this outputs "Hello", then immediately "World":
|
For instance, this outputs "Hello", then immediately "World":
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue