improvements
This commit is contained in:
parent
21a89ce15e
commit
2687001ee5
1 changed files with 48 additions and 23 deletions
|
@ -223,7 +223,7 @@ set.forEach((value, valueAgain, set) => {
|
|||
|
||||
Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a value, then *again a value*, and then the target object. Indeed, the same value appears in the arguments twice.
|
||||
|
||||
That's for compatibility with `Map` where `forEach` has three arguments.
|
||||
That's for compatibility with `Map` where `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa.
|
||||
|
||||
The same methods `Map` has for iterators are also supported:
|
||||
|
||||
|
@ -253,9 +253,27 @@ john = null;
|
|||
|
||||
Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory.
|
||||
|
||||
In a regular `Map`, it does not matter if we store an object as a key or as a value. It's kept in memory even if there are no more references to it.
|
||||
For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it.
|
||||
|
||||
Like this:
|
||||
|
||||
```js
|
||||
let john = { name: "John" };
|
||||
|
||||
let array = [ john ];
|
||||
|
||||
john = null; // overwrite the reference
|
||||
|
||||
*!*
|
||||
// john is stored inside the array, so it won't be garbage-collected
|
||||
// we can get it as array[0]
|
||||
*/!*
|
||||
```
|
||||
|
||||
Or, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected.
|
||||
|
||||
For instance:
|
||||
|
||||
```js
|
||||
let john = { name: "John" };
|
||||
|
||||
|
@ -265,19 +283,16 @@ map.set(john, "...");
|
|||
john = null; // overwrite the reference
|
||||
|
||||
*!*
|
||||
// john is stored inside the map
|
||||
// john is stored inside the map,
|
||||
// we can get it by using map.keys()
|
||||
*/!*
|
||||
```
|
||||
|
||||
`WeakMap/WeakSet` are fundamentally different in this aspect. They do not prevent garbage-collection of key objects.
|
||||
|
||||
With the exception of `WeakMap/WeakSet`.
|
||||
Let's explain it starting with `WeakMap`.
|
||||
|
||||
**`WeakMap/WeakSet` does not prevent the object removal from the memory.**
|
||||
|
||||
Let's start with `WeakMap`.
|
||||
|
||||
The first difference from `Map` is that its keys must be objects, not primitive values:
|
||||
The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values:
|
||||
|
||||
```js run
|
||||
let weakMap = new WeakMap();
|
||||
|
@ -287,7 +302,8 @@ let obj = {};
|
|||
weakMap.set(obj, "ok"); // works fine (object key)
|
||||
|
||||
*!*
|
||||
weakMap.set("test", "Whoops"); // Error, because "test" is a primitive
|
||||
// can't use a string as the key
|
||||
weakMap.set("test", "Whoops"); // Error, because "test" is not an object
|
||||
*/!*
|
||||
```
|
||||
|
||||
|
@ -306,7 +322,7 @@ john = null; // overwrite the reference
|
|||
|
||||
Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it is to be automatically deleted.
|
||||
|
||||
...And `WeakMap` does not support methods `keys()`, `values()`, `entries()`, we can not iterate over it. So there's really no way to receive all keys or values from it.
|
||||
`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it.
|
||||
|
||||
`WeakMap` has only the following methods:
|
||||
|
||||
|
@ -315,26 +331,26 @@ Compare it with the regular `Map` example above. Now if `john` only exists as th
|
|||
- `weakMap.delete(key, value)`
|
||||
- `weakMap.has(key)`
|
||||
|
||||
Why such a limitation? That's for technical reasons. If the object has lost all other references (like `john` in the code above), then it is to be deleted automatically. But technically it's not exactly specified *when the cleanup happens*.
|
||||
Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*.
|
||||
|
||||
The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of the `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access `WeakMap` as a whole are not supported.
|
||||
The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access `WeakMap` as a whole are not supported.
|
||||
|
||||
Now where do we need such thing?
|
||||
|
||||
The idea of `WeakMap` is that we can store something for an object that exists only while the object exists. But we do not force the object to live by the mere fact that we store something for it.
|
||||
The idea of `WeakMap` is that we can store something for an object that should exist only while the object exists. But we do not force the object to live by the mere fact that we store something for it.
|
||||
|
||||
```js
|
||||
weakMap.set(john, "secret documents");
|
||||
// if john dies, secret documents will be destroyed
|
||||
// if john dies, secret documents will be destroyed automatically
|
||||
```
|
||||
|
||||
That's useful for situations when we have a main storage for the objects somewhere and need to keep additional information that is only relevant while the object lives.
|
||||
That's useful for situations when we have a main storage for the objects somewhere and need to keep additional information, that is only relevant while the object lives.
|
||||
|
||||
Let's look at an example.
|
||||
|
||||
For instance, we have code that keeps a visit count for each user. The information is stored in a map: a user is the key and the visit count is the value. When a user leaves, we don't want to store their visit count anymore.
|
||||
|
||||
One way would be to keep track of leaving users and clean up the storage manually:
|
||||
One way would be to keep track of users, and when they leave -- clean up the map manually:
|
||||
|
||||
```js run
|
||||
let john = { name: "John" };
|
||||
|
@ -352,7 +368,7 @@ john = null;
|
|||
// but it's still in the map, we need to clean it!
|
||||
*/!*
|
||||
alert( visitsCountMap.size ); // 1
|
||||
// it's also in the memory, because Map uses it as the key
|
||||
// and john is also in the memory, because Map uses it as the key
|
||||
```
|
||||
|
||||
Another way would be to use `WeakMap`:
|
||||
|
@ -371,9 +387,11 @@ john = null;
|
|||
// so the object is removed both from the memory and from visitsCountMap automatically
|
||||
```
|
||||
|
||||
With a regular `Map`, cleaning up after a user has left becomes a tedious task: we not only need to remove the user from its main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals.
|
||||
With a regular `Map`, cleaning up after a user has left becomes a tedious task: we not only need to remove the user from its main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is in another place and is getting no information about removals.
|
||||
|
||||
```summary
|
||||
`WeakMap` can make things simpler, because it is cleaned up automatically. The information in it like visits count in the example above lives only while the key object exists.
|
||||
```
|
||||
|
||||
`WeakSet` behaves similarly:
|
||||
|
||||
|
@ -381,7 +399,7 @@ With a regular `Map`, cleaning up after a user has left becomes a tedious task:
|
|||
- An object exists in the set while it is reachable from somewhere else.
|
||||
- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations.
|
||||
|
||||
For instance, we can use it to keep track of whether an item is checked:
|
||||
For instance, we can use it to keep track of whether a message is read:
|
||||
|
||||
```js
|
||||
let messages = [
|
||||
|
@ -393,21 +411,26 @@ let messages = [
|
|||
// fill it with array elements (3 items)
|
||||
let unreadSet = new WeakSet(messages);
|
||||
|
||||
// we can use unreadSet to see whether a message is unread
|
||||
// use unreadSet to see whether a message is unread
|
||||
alert(unreadSet.has(messages[1])); // true
|
||||
|
||||
// remove it from the set after reading
|
||||
unreadSet.delete(messages[1]); // true
|
||||
|
||||
// and when we shift our messages history, the set is cleaned up automatically
|
||||
messages.shift();
|
||||
|
||||
*!*
|
||||
// no need to clean unreadSet, it now has 2 items
|
||||
// unfortunately, there's no method to get the exact count of items, so can't show it
|
||||
*/!*
|
||||
// (though technically we don't know for sure when the JS engine clears it)
|
||||
```
|
||||
|
||||
The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but actually does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place.
|
||||
The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place.
|
||||
|
||||
## Summary
|
||||
|
||||
Regular collections:
|
||||
- `Map` -- is a collection of keyed values.
|
||||
|
||||
The differences from a regular `Object`:
|
||||
|
@ -421,6 +444,8 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati
|
|||
- Unlike an array, does not allow to reorder elements.
|
||||
- Keeps the insertion order.
|
||||
|
||||
Collections that allow garbage-collection:
|
||||
|
||||
- `WeakMap` -- a variant of `Map` that allows only objects as keys and removes them once they become inaccessible by other means.
|
||||
|
||||
- It does not support operations on the structure as a whole: no `size`, no `clear()`, no iterations.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue