fixes
This commit is contained in:
parent
6782931a9e
commit
85e67ebb5b
4 changed files with 133 additions and 122 deletions
|
@ -92,35 +92,6 @@ map.set('1', 'str1')
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
## Map from Object
|
|
||||||
|
|
||||||
When a `Map` is created, we can pass an array (or another iterable) with key-value pairs for initialization, like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// array of [key, value] pairs
|
|
||||||
let map = new Map([
|
|
||||||
['1', 'str1'],
|
|
||||||
[1, 'num1'],
|
|
||||||
[true, 'bool1']
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format.
|
|
||||||
|
|
||||||
So we can initialize a map from an object like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let obj = {
|
|
||||||
name: "John",
|
|
||||||
age: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let map = new Map(Object.entries(obj));
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs.
|
|
||||||
|
|
||||||
## Iteration over Map
|
## Iteration over Map
|
||||||
|
|
||||||
|
@ -168,6 +139,90 @@ recipeMap.forEach( (value, key, map) => {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Object.entries: Map from Object
|
||||||
|
|
||||||
|
When a `Map` is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
// array of [key, value] pairs
|
||||||
|
let map = new Map([
|
||||||
|
['1', 'str1'],
|
||||||
|
[1, 'num1'],
|
||||||
|
[true, 'bool1']
|
||||||
|
]);
|
||||||
|
|
||||||
|
alert( map.get('1') ); // str1
|
||||||
|
```
|
||||||
|
|
||||||
|
If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format.
|
||||||
|
|
||||||
|
So we can create a map from an object like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let obj = {
|
||||||
|
name: "John",
|
||||||
|
age: 30
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let map = new Map(Object.entries(obj));
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( map.get('name') ); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs.
|
||||||
|
|
||||||
|
|
||||||
|
## Object.fromEntries: Object from Map
|
||||||
|
|
||||||
|
We've just seen how to create `Map` from a plain object with `Object.entries(obj)`.
|
||||||
|
|
||||||
|
There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let prices = Object.fromEntries([
|
||||||
|
['banana', 1],
|
||||||
|
['orange', 2],
|
||||||
|
['meat', 4]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// now prices = { banana: 1, orange: 2, meat: 4 }
|
||||||
|
|
||||||
|
alert(prices.orange); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
We can use `Object.fromEntries` to get an plain object from `Map`.
|
||||||
|
|
||||||
|
E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object.
|
||||||
|
|
||||||
|
Here we go:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let map = new Map();
|
||||||
|
map.set('banana', 1);
|
||||||
|
map.set('orange', 2);
|
||||||
|
map.set('meat', 4);
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let obj = Object.fromEntries(map.entries()); // make a plain object (*)
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
// done!
|
||||||
|
// obj = { banana: 1, orange: 2, meat: 4 }
|
||||||
|
|
||||||
|
alert(obj.orange); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
A call to `map.entries()` returns an array of key/value pairs, exactly in the right format for `Object.fromEntries`.
|
||||||
|
|
||||||
|
We could also make line `(*)` shorter:
|
||||||
|
```js
|
||||||
|
let obj = Object.fromEntries(map); // omit .entries()
|
||||||
|
```
|
||||||
|
|
||||||
|
That's the same, because `Object.fromEntries` expects an iterable object as the argument. Not necessarily an array. And the standard iteration for `map` returns same key/value pairs as `map.entries()`. So we get a plain object with same key/values as the `map`.
|
||||||
|
|
||||||
## Set
|
## Set
|
||||||
|
|
||||||
A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once.
|
A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once.
|
||||||
|
|
|
@ -282,7 +282,7 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati
|
||||||
|
|
||||||
`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means.
|
`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means.
|
||||||
|
|
||||||
`WeakSet` is `Set`-like collection that only stores objects and removes them once they become inaccessible by other means.
|
`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means.
|
||||||
|
|
||||||
Both of them do not support methods and properties that refer to all keys or their count. Only individial operations are allowed.
|
Both of them do not support methods and properties that refer to all keys or their count. Only individial operations are allowed.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ For plain objects, the following methods are available:
|
||||||
- [Object.values(obj)](mdn:js/Object/values) -- returns an array of values.
|
- [Object.values(obj)](mdn:js/Object/values) -- returns an array of values.
|
||||||
- [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs.
|
- [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs.
|
||||||
|
|
||||||
...But please note the distinctions (compared to map for example):
|
Please note the distinctions (compared to map for example):
|
||||||
|
|
||||||
| | Map | Object |
|
| | Map | Object |
|
||||||
|-------------|------------------|--------------|
|
|-------------|------------------|--------------|
|
||||||
|
@ -32,7 +32,7 @@ For plain objects, the following methods are available:
|
||||||
|
|
||||||
The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`.
|
The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`.
|
||||||
|
|
||||||
Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `order` that implements its own `order.values()` method. And we still can call `Object.values(order)` on it.
|
Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `data` that implements its own `data.values()` method. And we still can call `Object.values(data)` on it.
|
||||||
|
|
||||||
The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons.
|
The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons.
|
||||||
|
|
||||||
|
@ -69,55 +69,21 @@ Just like a `for..in` loop, these methods ignore properties that use `Symbol(...
|
||||||
Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys.
|
Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Object.fromEntries to transform objects
|
|
||||||
|
|
||||||
Sometimes we need to perform a transformation of an object to `Map` and back.
|
## Transforming objects
|
||||||
|
|
||||||
We already have `new Map(Object.entries(obj))` to make a `Map` from `obj`.
|
Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others.
|
||||||
|
|
||||||
The syntax of `Object.fromEntries` does the reverse. Given an array of `[key, value]` pairs, it creates an object:
|
If we'd like to apply them, then we can use `Object.entries` followed `Object.fromEntries`:
|
||||||
|
|
||||||
|
1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`.
|
||||||
|
2. Use array methods on that array, e.g. `map`.
|
||||||
|
3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object.
|
||||||
|
|
||||||
|
For example, we have an object with prices, and would like to double them:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let prices = Object.fromEntries([
|
let users = {
|
||||||
['banana', 1],
|
|
||||||
['orange', 2],
|
|
||||||
['meat', 4]
|
|
||||||
]);
|
|
||||||
|
|
||||||
// now prices = { banana: 1, orange: 2, meat: 4 }
|
|
||||||
|
|
||||||
alert(prices.orange); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's see practical applications.
|
|
||||||
|
|
||||||
For example, we'd like to create a new object with double prices from the existing one.
|
|
||||||
|
|
||||||
For arrays, we have `.map` method that allows to transform an array, but nothing like that for objects.
|
|
||||||
|
|
||||||
So we can use a loop:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let prices = {
|
|
||||||
banana: 1,
|
|
||||||
orange: 2,
|
|
||||||
meat: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
let doublePrices = {};
|
|
||||||
for(let [product, price] of Object.entries(prices)) {
|
|
||||||
doublePrices[product] = price * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(doublePrices.meat); // 8
|
|
||||||
```
|
|
||||||
|
|
||||||
...Or we can represent the object as an `Array` using `Object.entries`, then perform the operations with `map` (and potentially other array methods), and then go back using `Object.fromEntries`.
|
|
||||||
|
|
||||||
Let's do it for our object:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let prices = {
|
|
||||||
banana: 1,
|
banana: 1,
|
||||||
orange: 2,
|
orange: 2,
|
||||||
meat: 4,
|
meat: 4,
|
||||||
|
@ -135,21 +101,4 @@ alert(doublePrices.meat); // 8
|
||||||
|
|
||||||
It may look difficult from the first sight, but becomes easy to understand after you use it once or twice.
|
It may look difficult from the first sight, but becomes easy to understand after you use it once or twice.
|
||||||
|
|
||||||
We also can use `fromEntries` to get an object from `Map`.
|
We can make powerful one-liners for more complex transforms this way. It's only important to keep balance, so that the code is still simple enough to understand it.
|
||||||
|
|
||||||
E.g. we have a `Map` of prices, but we need to pass it to a 3rd-party code that expects an object.
|
|
||||||
|
|
||||||
Here we go:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let map = new Map();
|
|
||||||
map.set('banana', 1);
|
|
||||||
map.set('orange', 2);
|
|
||||||
map.set('meat', 4);
|
|
||||||
|
|
||||||
let obj = Object.fromEntries(map);
|
|
||||||
|
|
||||||
// now obj = { banana: 1, orange: 2, meat: 4 }
|
|
||||||
|
|
||||||
alert(obj.orange); // 2
|
|
||||||
```
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ alert(height); // 200
|
||||||
|
|
||||||
Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided.
|
Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided.
|
||||||
|
|
||||||
The code below asks for width, but not the title.
|
In the code below `prompt` asks for `width`, but not for `title`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let options = {
|
let options = {
|
||||||
|
@ -293,6 +293,21 @@ alert(w); // 100
|
||||||
alert(h); // 200
|
alert(h); // 200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If we have a complex object with many properties, we can extract only what we need:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let options = {
|
||||||
|
title: "Menu",
|
||||||
|
width: 100,
|
||||||
|
height: 200
|
||||||
|
};
|
||||||
|
|
||||||
|
// only extract title as a variable
|
||||||
|
let { title } = options;
|
||||||
|
|
||||||
|
alert(title); // Menu
|
||||||
|
```
|
||||||
|
|
||||||
### The rest pattern "..."
|
### The rest pattern "..."
|
||||||
|
|
||||||
What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere?
|
What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere?
|
||||||
|
@ -319,8 +334,6 @@ alert(rest.height); // 200
|
||||||
alert(rest.width); // 100
|
alert(rest.width); // 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
````smart header="Gotcha if there's no `let`"
|
````smart header="Gotcha if there's no `let`"
|
||||||
In the examples above variables were declared right in the assignment: `let {…} = {…}`. Of course, we could use existing variables too, without `let`. But there's a catch.
|
In the examples above variables were declared right in the assignment: `let {…} = {…}`. Of course, we could use existing variables too, without `let`. But there's a catch.
|
||||||
|
|
||||||
|
@ -343,7 +356,9 @@ The problem is that JavaScript treats `{...}` in the main code flow (not inside
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To show JavaScript that it's not a code block, we can make it a part of an expression by wrapping in parentheses `(...)`:
|
So here JavaScript assumes that we have a code block, but why there's an error. We have destructuring instead.
|
||||||
|
|
||||||
|
To show JavaScript that it's not a code block, we can wrap the expression in parentheses `(...)`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let title, width, height;
|
let title, width, height;
|
||||||
|
@ -353,14 +368,13 @@ let title, width, height;
|
||||||
|
|
||||||
alert( title ); // Menu
|
alert( title ); // Menu
|
||||||
```
|
```
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
## Nested destructuring
|
## Nested destructuring
|
||||||
|
|
||||||
If an object or an array contain other objects and arrays, we can use more complex left-side patterns to extract deeper portions.
|
If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.
|
||||||
|
|
||||||
In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure:
|
In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let options = {
|
let options = {
|
||||||
|
@ -369,7 +383,7 @@ let options = {
|
||||||
height: 200
|
height: 200
|
||||||
},
|
},
|
||||||
items: ["Cake", "Donut"],
|
items: ["Cake", "Donut"],
|
||||||
extra: true // something extra that we will not destruct
|
extra: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// destructuring assignment split in multiple lines for clarity
|
// destructuring assignment split in multiple lines for clarity
|
||||||
|
@ -389,20 +403,13 @@ alert(item1); // Cake
|
||||||
alert(item2); // Donut
|
alert(item2); // Donut
|
||||||
```
|
```
|
||||||
|
|
||||||
The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables.
|
The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables:
|
||||||
|
|
||||||
Note that `size` and `items` itself is not destructured.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value.
|
Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value.
|
||||||
|
|
||||||
If we have a complex object with many properties, we can extract only what we need:
|
Note that there are no variables for `size` and `items`, as we take their content instead.
|
||||||
|
|
||||||
```js
|
|
||||||
// take size as a whole into a variable, ignore the rest
|
|
||||||
let { size } = options;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Smart function parameters
|
## Smart function parameters
|
||||||
|
|
||||||
|
@ -421,6 +428,7 @@ In real-life, the problem is how to remember the order of arguments. Usually IDE
|
||||||
Like this?
|
Like this?
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// undefined where detauls values are fine
|
||||||
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
|
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -472,29 +480,28 @@ function showMenu({
|
||||||
showMenu(options);
|
showMenu(options);
|
||||||
```
|
```
|
||||||
|
|
||||||
The syntax is the same as for a destructuring assignment:
|
The full syntax is the same as for a destructuring assignment:
|
||||||
```js
|
```js
|
||||||
function({
|
function({
|
||||||
incomingProperty: parameterName = defaultValue
|
incomingProperty: varName = defaultValue
|
||||||
...
|
...
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default.
|
||||||
|
|
||||||
Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object:
|
Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
showMenu({});
|
showMenu({}); // ok, all values are default
|
||||||
|
|
||||||
|
|
||||||
showMenu(); // this would give an error
|
showMenu(); // this would give an error
|
||||||
```
|
```
|
||||||
|
|
||||||
We can fix this by making `{}` the default value for the whole destructuring thing:
|
We can fix this by making `{}` the default value for the whole object of parameters:
|
||||||
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// simplified parameters a bit for clarity
|
function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) {
|
||||||
function showMenu(*!*{ title = "Menu", width = 100, height = 200 } = {}*/!*) {
|
|
||||||
alert( `${title} ${width} ${height}` );
|
alert( `${title} ${width} ${height}` );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,7 +513,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- Destructuring assignment allows for instantly mapping an object or array onto many variables.
|
- Destructuring assignment allows for instantly mapping an object or array onto many variables.
|
||||||
- The object syntax:
|
- The full object syntax:
|
||||||
```js
|
```js
|
||||||
let {prop : varName = default, ...rest} = object
|
let {prop : varName = default, ...rest} = object
|
||||||
```
|
```
|
||||||
|
@ -515,7 +522,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
|
||||||
|
|
||||||
Object properties that have no mapping are copied to the `rest` object.
|
Object properties that have no mapping are copied to the `rest` object.
|
||||||
|
|
||||||
- The array syntax:
|
- The full array syntax:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let [item1 = default, item2, ...rest] = array
|
let [item1 = default, item2, ...rest] = array
|
||||||
|
@ -523,4 +530,4 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
|
||||||
|
|
||||||
The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`.
|
The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`.
|
||||||
|
|
||||||
- For more complex cases, the left side must have the same structure as the right one.
|
- It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue