This commit is contained in:
Ilya Kantor 2019-07-31 14:14:27 +03:00
parent 6782931a9e
commit 85e67ebb5b
4 changed files with 133 additions and 122 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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
```

View file

@ -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.
![](destructuring-complex.svg) ![](destructuring-complex.svg)
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.