From 85e67ebb5bc491b3a2de9f1a6b38829af8c94ee5 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 31 Jul 2019 14:14:27 +0300 Subject: [PATCH] fixes --- 1-js/05-data-types/07-map-set/article.md | 113 +++++++++++++----- .../08-weakmap-weakset/article.md | 2 +- .../09-keys-values-entries/article.md | 77 ++---------- .../10-destructuring-assignment/article.md | 63 +++++----- 4 files changed, 133 insertions(+), 122 deletions(-) diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 044d6392..c4d7c21a 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -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 @@ -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 A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 4397270b..7dbe097c 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -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. -`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. diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index 3780b33e..38ed99e5 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -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.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 | |-------------|------------------|--------------| @@ -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()`. -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. @@ -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. ``` -## 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 -let prices = Object.fromEntries([ - ['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 = { +let users = { banana: 1, orange: 2, 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. -We also can use `fromEntries` to get an object from `Map`. - -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 -``` +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. diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index ddd3f167..762e5a12 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -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. -The code below asks for width, but not the title. +In the code below `prompt` asks for `width`, but not for `title`: ```js run let options = { @@ -293,6 +293,21 @@ alert(w); // 100 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 "..." 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 ``` - - ````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. @@ -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 let title, width, height; @@ -353,14 +368,13 @@ let title, width, height; alert( title ); // Menu ``` - ```` ## 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 let options = { @@ -369,7 +383,7 @@ let options = { height: 200 }, items: ["Cake", "Donut"], - extra: true // something extra that we will not destruct + extra: true }; // destructuring assignment split in multiple lines for clarity @@ -389,20 +403,13 @@ alert(item1); // Cake alert(item2); // Donut ``` -The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables. - -Note that `size` and `items` itself is not destructured. +The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables: ![](destructuring-complex.svg) 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: - -```js -// take size as a whole into a variable, ignore the rest -let { size } = options; -``` +Note that there are no variables for `size` and `items`, as we take their content instead. ## Smart function parameters @@ -421,6 +428,7 @@ In real-life, the problem is how to remember the order of arguments. Usually IDE Like this? ```js +// undefined where detauls values are fine showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` @@ -472,29 +480,28 @@ function showMenu({ showMenu(options); ``` -The syntax is the same as for a destructuring assignment: +The full syntax is the same as for a destructuring assignment: ```js 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: ```js -showMenu({}); - +showMenu({}); // ok, all values are default 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 -// 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}` ); } @@ -506,7 +513,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw ## Summary - Destructuring assignment allows for instantly mapping an object or array onto many variables. -- The object syntax: +- The full object syntax: ```js 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. -- The array syntax: +- The full array syntax: ```js 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`. -- 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.