diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md deleted file mode 100644 index 7ec1faf16..000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md +++ /dev/null @@ -1,23 +0,0 @@ -importance: 5 - ---- - -# Store "unread" flags - -There's an array of messages: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; -``` - -Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens. - -Now, which data structure you could use to store information whether the message "have been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. - -P.S. When a message is removed from `messages`, it should disappear from your structure as well. - -P.P.S. We shouldn't modify message objects directly. If they are managed by someone else's code, then adding extra properties to them may have bad consequences. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md b/1-js/05-data-types/07-map-set-weakmap-weakset/article.md deleted file mode 100644 index 034ad22c3..000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md +++ /dev/null @@ -1,458 +0,0 @@ - -# Map, Set, WeakMap and WeakSet - -Now we've learned about the following complex data structures: - -- Objects for storing keyed collections. -- Arrays for storing ordered collections. - -But that's not enough for real life. That's why `Map` and `Set` also exist. - -## Map - -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. - -The main methods are: - -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- clears the map -- `map.size` -- returns the current element count. - -For instance: - -```js run -let map = new Map(); - -map.set('1', 'str1'); // a string key -map.set(1, 'num1'); // a numeric key -map.set(true, 'bool1'); // a boolean key - -// remember the regular Object? it would convert keys to string -// Map keeps the type, so these two are different: -alert( map.get(1) ); // 'num1' -alert( map.get('1') ); // 'str1' - -alert( map.size ); // 3 -``` - -As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. - -**Map can also use objects as keys.** - -For instance: -```js run -let john = { name: "John" }; - -// for every user, let's store their visits count -let visitsCountMap = new Map(); - -// john is the key for the map -visitsCountMap.set(john, 123); - -alert( visitsCountMap.get(john) ); // 123 -``` - -Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but it would be difficult to replace the `Map` with a regular `Object` in the example above. - -In the old times, before `Map` existed, people added unique identifiers to objects for that: - -```js run -// we add the id field -let john = { name: "John", *!*id: 1*/!* }; - -let visitsCounts = {}; - -// now store the value by id -visitsCounts[john.id] = 123; - -alert( visitsCounts[john.id] ); // 123 -``` - -...But `Map` is much more elegant. - - -```smart header="How `Map` compares keys" -To test values for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. - -This algorithm can't be changed or customized. -``` - - -````smart header="Chaining" - -Every `map.set` call returns the map itself, so we can "chain" the calls: - -```js -map.set('1', 'str1') - .set(1, 'num1') - .set(true, 'bool1'); -``` -```` - -## Map from Object - -When a `Map` is created, we can pass an array (or another iterable) with key-value pairs, like this: - -```js -// array of [key, value] pairs -let map = new Map([ - ['1', 'str1'], - [1, 'num1'], - [true, 'bool1'] -]); -``` - -There is a 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 map = new Map(Object.entries({ - name: "John", - age: 30 -})); -``` - -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. - -## Iteration over Map - -For looping over a `map`, there are 3 methods: - -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. - -For instance: - -```js run -let recipeMap = new Map([ - ['cucumber', 500], - ['tomatoes', 350], - ['onion', 50] -]); - -// iterate over keys (vegetables) -for (let vegetable of recipeMap.keys()) { - alert(vegetable); // cucumber, tomatoes, onion -} - -// iterate over values (amounts) -for (let amount of recipeMap.values()) { - alert(amount); // 500, 350, 50 -} - -// iterate over [key, value] entries -for (let entry of recipeMap) { // the same as of recipeMap.entries() - alert(entry); // cucumber,500 (and so on) -} -``` - -```smart header="The insertion order is used" -The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. -``` - -Besides that, `Map` has a built-in `forEach` method, similar to `Array`: - -```js -// runs the function for each (key, value) pair -recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 etc -}); -``` - - -## Set - -A `Set` is a collection of values, where each value may occur only once. - -Its main methods are: - -- `new Set(iterable)` -- creates the set, optionally from an array of values (any iterable will do). -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. - -For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. - -`Set` is just the right thing for that: - -```js run -let set = new Set(); - -let john = { name: "John" }; -let pete = { name: "Pete" }; -let mary = { name: "Mary" }; - -// visits, some users come multiple times -set.add(john); -set.add(pete); -set.add(mary); -set.add(john); -set.add(mary); - -// set keeps only unique values -alert( set.size ); // 3 - -for (let user of set) { - alert(user.name); // John (then Pete and Mary) -} -``` - -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. - -## Iteration over Set - -We can loop over a set either with `for..of` or using `forEach`: - -```js run -let set = new Set(["oranges", "apples", "bananas"]); - -for (let value of set) alert(value); - -// the same with forEach: -set.forEach((value, valueAgain, set) => { - alert(value); -}); -``` - -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. 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: - -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. - -## 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 , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). - -For instance: -```js -let john = { name: "John" }; - -// the object can be accessed, john is the reference to it - -// overwrite the reference -john = null; - -*!* -// the object will be removed from memory -*/!* -``` - -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. - -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" }; - -let map = new Map(); -map.set(john, "..."); - -john = null; // overwrite the reference - -*!* -// 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. - -Let's explain it starting with `WeakMap`. - -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: - -```js run -let weakMap = new WeakMap(); - -let obj = {}; - -weakMap.set(obj, "ok"); // works fine (object key) - -*!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object -*/!* -``` - -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. - -```js -let john = { name: "John" }; - -let weakMap = new WeakMap(); -weakMap.set(john, "..."); - -john = null; // overwrite the reference - -// john is removed from memory! -``` - -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it is to be automatically deleted. - -`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: - -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` - -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 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 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 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. - -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 users, and when they leave -- clean up the map manually: - -```js run -let john = { name: "John" }; - -// map: user => visits count -let visitsCountMap = new Map(); - -// john is the key for the map -visitsCountMap.set(john, 123); - -// now john leaves us, we don't need him anymore -john = null; - -*!* -// but it's still in the map, we need to clean it! -*/!* -alert( visitsCountMap.size ); // 1 -// and john is also in the memory, because Map uses it as the key -``` - -Another way would be to use `WeakMap`: - -```js -let john = { name: "John" }; - -let visitsCountMap = new WeakMap(); - -visitsCountMap.set(john, 123); - -// now john leaves us, we don't need him anymore -john = null; - -// there are no references except WeakMap, -// 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 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: - -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- 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 a message is read: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; - -// fill it with array elements (3 items) -let unreadSet = new WeakSet(messages); - -// 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 -*/!* -// (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 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`: - - - Any keys, objects can be keys. - - Iterates in the insertion order. - - Additional convenient methods, the `size` property. - -- `Set` -- is a collection of unique values. - - - 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. - -- `WeakSet` -- is a variant of `Set` that only stores objects and removes them once they become inaccessible by other means. - - - Also does not support `size/clear()` and iterations. - -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found in the `WeakMap/WeakSet`, it will be cleaned up automatically. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/solution.js b/1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/solution.js rename to 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/solution.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/test.js b/1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/test.js rename to 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/test.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/solution.md b/1-js/05-data-types/07-map-set/01-array-unique-map/solution.md similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/solution.md rename to 1-js/05-data-types/07-map-set/01-array-unique-map/solution.md diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/task.md b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/task.md rename to 1-js/05-data-types/07-map-set/01-array-unique-map/task.md diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/solution.js b/1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/solution.js rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/solution.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/test.js b/1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/test.js rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/test.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md similarity index 98% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md index 4c8af1f24..160675185 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -36,7 +36,7 @@ Letter-sorting is done by the chain of calls in the line `(*)`. For convenience let's split it into multiple lines: ```js -let sorted = arr[i] // PAN +let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/task.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/task.md rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/task.md diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/solution.md b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/solution.md rename to 1-js/05-data-types/07-map-set/03-iterable-keys/solution.md diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md similarity index 63% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md rename to 1-js/05-data-types/07-map-set/03-iterable-keys/task.md index b1ccbd0ac..25c74bfc2 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -4,9 +4,9 @@ importance: 5 # Iterable keys -We want to get an array of `map.keys()` and go on working with it (apart from the map itself). +We'd like to get an array of `map.keys()` in a variable and then do apply array-specific methods to it, e.g. `.push`. -But there's a problem: +But that doesn't work: ```js run let map = new Map(); diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md new file mode 100644 index 000000000..aaed5b454 --- /dev/null +++ b/1-js/05-data-types/07-map-set/article.md @@ -0,0 +1,330 @@ + +# Map and Set + +Now we've learned about the following complex data structures: + +- Objects for storing keyed collections. +- Arrays for storing ordered collections. + +But that's not enough for real life. That's why `Map` and `Set` also exist. + +## Map + +[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. + +Methods and properties are: + +- `new Map()` -- creates the map. +- `map.set(key, value)` -- stores the value by the key. +- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. +- `map.delete(key)` -- removes the value by the key. +- `map.clear()` -- removes everything from the map. +- `map.size` -- returns the current element count. + +For instance: + +```js run +let map = new Map(); + +map.set('1', 'str1'); // a string key +map.set(1, 'num1'); // a numeric key +map.set(true, 'bool1'); // a boolean key + +// remember the regular Object? it would convert keys to string +// Map keeps the type, so these two are different: +alert( map.get(1) ); // 'num1' +alert( map.get('1') ); // 'str1' + +alert( map.size ); // 3 +``` + +As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. + +```smart header="`map[key]` isn't the right way to use a `Map`" +Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on). + +So we should use `map` methods: `set`, `get` and so on. +``` + +**Map can also use objects as keys.** + +For instance: + +```js run +let john = { name: "John" }; + +// for every user, let's store their visits count +let visitsCountMap = new Map(); + +// john is the key for the map +visitsCountMap.set(john, 123); + +alert( visitsCountMap.get(john) ); // 123 +``` + +Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but not for object keys. + +Let's try: + +```js run +let john = { name: "John" }; + +let visitsCountObj = {}; // try to use an object + +visitsCountObj[john] = 123; // try to use john object as the key + +*!* +// That's what got written! +alert( visitsCountObj["[object Object]"] ); // 123 +*/!* +``` + +As `visitsCountObj` is an object, it converts all keys, such as `john` to strings, so we've got the string key `"[object Object]"`. Definitely not what we want. + +```smart header="How `Map` compares keys" +To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. + +This algorithm can't be changed or customized. +``` + +````smart header="Chaining" +Every `map.set` call returns the map itself, so we can "chain" the calls: + +```js +map.set('1', 'str1') + .set(1, 'num1') + .set(true, 'bool1'); +``` +```` + + +## Iteration over Map + +For looping over a `map`, there are 3 methods: + +- `map.keys()` -- returns an iterable for keys, +- `map.values()` -- returns an iterable for values, +- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. + +For instance: + +```js run +let recipeMap = new Map([ + ['cucumber', 500], + ['tomatoes', 350], + ['onion', 50] +]); + +// iterate over keys (vegetables) +for (let vegetable of recipeMap.keys()) { + alert(vegetable); // cucumber, tomatoes, onion +} + +// iterate over values (amounts) +for (let amount of recipeMap.values()) { + alert(amount); // 500, 350, 50 +} + +// iterate over [key, value] entries +for (let entry of recipeMap) { // the same as of recipeMap.entries() + alert(entry); // cucumber,500 (and so on) +} +``` + +```smart header="The insertion order is used" +The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. +``` + +Besides that, `Map` has a built-in `forEach` method, similar to `Array`: + +```js +// runs the function for each (key, value) pair +recipeMap.forEach( (value, key, map) => { + alert(`${key}: ${value}`); // cucumber: 500 etc +}); +``` + +## 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. + +Its main methods are: + +- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- `set.add(value)` -- adds a value, returns the set itself. +- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. +- `set.clear()` -- removes everything from the set. +- `set.size` -- is the elements count. + +The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. + +For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. + +`Set` is just the right thing for that: + +```js run +let set = new Set(); + +let john = { name: "John" }; +let pete = { name: "Pete" }; +let mary = { name: "Mary" }; + +// visits, some users come multiple times +set.add(john); +set.add(pete); +set.add(mary); +set.add(john); +set.add(mary); + +// set keeps only unique values +alert( set.size ); // 3 + +for (let user of set) { + alert(user.name); // John (then Pete and Mary) +} +``` + +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. + +## Iteration over Set + +We can loop over a set either with `for..of` or using `forEach`: + +```js run +let set = new Set(["oranges", "apples", "bananas"]); + +for (let value of set) alert(value); + +// the same with forEach: +set.forEach((value, valueAgain, set) => { + alert(value); +}); +``` + +Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. + +That's for compatibility with `Map` where the callback passed `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: + +- `set.keys()` -- returns an iterable object for values, +- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, +- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. + +## Summary + +`Map` -- is a collection of keyed values. + +Methods and properties: + +- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- `map.set(key, value)` -- stores the value by the key. +- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. +- `map.delete(key)` -- removes the value by the key. +- `map.clear()` -- removes everything from the map. +- `map.size` -- returns the current element count. + +The differences from a regular `Object`: + +- Any keys, objects can be keys. +- Additional convenient methods, the `size` property. + +`Set` -- is a collection of unique values. + +Methods and properties: + +- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. +- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. +- `set.clear()` -- removes everything from the set. +- `set.size` -- is the elements count. + +Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md similarity index 51% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md rename to 1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index ce56f593a..6a4c20baf 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -1,10 +1,10 @@ -The sane choice here is a `WeakSet`: +Let's store read messages in `WeakSet`: -```js +```js run let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} ]; let readMessages = new WeakSet(); @@ -27,9 +27,9 @@ messages.shift(); The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. -It cleans up itself automatically. The tradeoff is that we can't iterate over it. We can't get "all read messages" directly. But we can do it by iterating over all messages and filtering those that are in the set. +It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. -P.S. Adding a property of our own to each message may be dangerous if messages are managed by someone else's code, but we can make it a symbol to evade conflicts. +Another, different solution could be to add a property like `message.isRead=true` to a message after it's read. As messages objects are managed by another code, that's generally discouraged, but we can use a symbolic property to avoid conflicts. Like this: ```js @@ -38,4 +38,6 @@ let isRead = Symbol("isRead"); messages[0][isRead] = true; ``` -Now even if someone else's code uses `for..in` loop for message properties, our secret flag won't appear. +Now third-party code probably won't see our extra property. + +Although symbols allow to lower the probability of problems, using `WeakSet` is better from the architectural point of view. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md new file mode 100644 index 000000000..fd31a891b --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Store "unread" flags + +There's an array of messages: + +```js +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; +``` + +Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens. + +Now, which data structure could you use to store information about whether the message "has been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. + +P.S. When a message is removed from `messages`, it should disappear from your structure as well. + +P.P.S. We shouldn't modify message objects, add our properties to them. As they are managed by someone else's code, that may lead to bad consequences. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md similarity index 61% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md rename to 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md index 7f387b4da..2af0547c1 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md @@ -3,9 +3,9 @@ To store a date, we can use `WeakMap`: ```js let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} ]; let readMap = new WeakMap(); diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md similarity index 54% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md rename to 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md index 22b51a382..8e341c184 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md @@ -8,12 +8,14 @@ There's an array of messages as in the [previous task](info:task/recipients-read ```js let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} ]; ``` The question now is: which data structure you'd suggest to store the information: "when the message was read?". -In the previous task we only needed to store the "yes/no" fact. Now we need to store the date and it, once again, should disappear if the message is gone. +In the previous task we only needed to store the "yes/no" fact. Now we need to store the date, and it should only remain in memory until the message is garbage collected. + +P.S. Dates can be stored as objects of built-in `Date` class, that we'll cover later. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md new file mode 100644 index 000000000..bcc5e5e6f --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -0,0 +1,288 @@ +# WeakMap and WeakSet + +As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). + +For instance: +```js +let john = { name: "John" }; + +// the object can be accessed, john is the reference to it + +// overwrite the reference +john = null; + +*!* +// the object will be removed from memory +*/!* +``` + +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. + +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] +*/!* +``` + +Similar to that, 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" }; + +let map = new Map(); +map.set(john, "..."); + +john = null; // overwrite the reference + +*!* +// john is stored inside the map, +// we can get it by using map.keys() +*/!* +``` + +`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. + +Let's see what it means on examples. + +## WeakMap + +The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: + +```js run +let weakMap = new WeakMap(); + +let obj = {}; + +weakMap.set(obj, "ok"); // works fine (object key) + +*!* +// can't use a string as the key +weakMap.set("test", "Whoops"); // Error, because "test" is not an object +*/!* +``` + +Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. + +```js +let john = { name: "John" }; + +let weakMap = new WeakMap(); +weakMap.set(john, "..."); + +john = null; // overwrite the reference + +// john is removed from memory! +``` + +Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it will be automatically deleted from the map (and memory). + +`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: + +- `weakMap.get(key)` +- `weakMap.set(key, value)` +- `weakMap.delete(key)` +- `weakMap.has(key)` + +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 a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. + +Now where do we need such data structure? + +## Use case: additional data + +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 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. + +```js +weakMap.set(john, "secret documents"); +// if john dies, secret documents will be destroyed automatically +``` + +Let's look at an example. + +For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore. + +Here's an example of a counting function with `Map`: + +```js +// 📁 visitsCount.js +let visitsCountMap = new Map(); // map: user => visits count + +// increase the visits count +function countUser(user) { + let count = visitsCountMap.get(user) || 0; + visitsCountMap.set(user, count + 1); +} +``` + +And here's another part of the code, maybe another file using it: + +```js +// 📁 main.js +let john = { name: "John" }; + +countUser(john); // count his visits + +// later john leaves us +john = null; +``` + +Now `john` object should be garbage collected, but remains in 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 can avoid it by switching to `WeakMap` instead: + +```js +// 📁 visitsCount.js +let visitsCountMap = new WeakMap(); // weakmap: user => visits count + +// increase the visits count +function countUser(user) { + let count = visitsCountMap.get(user) || 0; + visitsCountMap.set(user, count + 1); +} +``` + +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 + +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` to store results, like this: + +```js run +// 📁 cache.js +let cache = new Map(); + +// calculate and remember the result +function process(obj) { + if (!cache.has(obj)) { + let result = /* calculations of the result for */ obj; + + cache.set(obj, result); + } + + return cache.get(obj); +} + +*!* +// Now we use process() in another file: +*/!* + +// 📁 main.js +let obj = {/* let's say we have an object */}; + +let result1 = process(obj); // calculated + +// ...later, from another place of the code... +let result2 = process(obj); // remembered result taken from cache + +// ...later, when the object is not needed any more: +obj = null; + +alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) +``` + +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 this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected. + +```js run +// 📁 cache.js +*!* +let cache = new WeakMap(); +*/!* + +// calculate and remember the result +function process(obj) { + if (!cache.has(obj)) { + let result = /* calculate the result for */ obj; + + cache.set(obj, result); + } + + return cache.get(obj); +} + +// 📁 main.js +let obj = {/* some object */}; + +let result1 = process(obj); +let result2 = process(obj); + +// ...later, when the object is not needed any more: +obj = null; + +// 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 +``` + +## WeakSet + +`WeakSet` behaves similarly: + +- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). +- 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. + +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 add users to `WeakSet` to keep track of those who visited our site: + +```js run +let visitedSet = new WeakSet(); + +let john = { name: "John" }; +let pete = { name: "Pete" }; +let mary = { name: "Mary" }; + +visitedSet.add(john); // John visited us +visitedSet.add(pete); // Then Pete +visitedSet.add(john); // John again + +// visitedSet has 2 users now + +// check if John visited? +alert(visitedSet.has(john)); // true + +// check if Mary visited? +alert(visitedSet.has(mary)); // false + +john = null; + +// visitedSet will be cleaned automatically +``` + +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 + +`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 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 individual operations are allowed. + +`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/solution.js b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/solution.js rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/solution.js diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/test.js b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/test.js rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/test.js diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/task.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/task.md rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/solution.js b/1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/solution.js rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/solution.js diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/test.js b/1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/test.js rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/test.js diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/solution.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/solution.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/solution.md rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/solution.md diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/task.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/task.md rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md diff --git a/1-js/05-data-types/08-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/article.md rename to 1-js/05-data-types/09-keys-values-entries/article.md diff --git a/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/solution.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/1-destruct-user/solution.md rename to 1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md diff --git a/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/task.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/1-destruct-user/task.md rename to 1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/test.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/test.js rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/test.js diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/solution.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/solution.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/solution.md rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/solution.md diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/task.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/task.md rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md diff --git a/1-js/05-data-types/09-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/article.md rename to 1-js/05-data-types/10-destructuring-assignment/article.md diff --git a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.svg b/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/destructuring-complex.svg rename to 1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg diff --git a/1-js/05-data-types/10-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/1-new-date/solution.md rename to 1-js/05-data-types/11-date/1-new-date/solution.md diff --git a/1-js/05-data-types/10-date/1-new-date/task.md b/1-js/05-data-types/11-date/1-new-date/task.md similarity index 100% rename from 1-js/05-data-types/10-date/1-new-date/task.md rename to 1-js/05-data-types/11-date/1-new-date/task.md diff --git a/1-js/05-data-types/10-date/2-get-week-day/_js.view/solution.js b/1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/2-get-week-day/_js.view/solution.js rename to 1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js diff --git a/1-js/05-data-types/10-date/2-get-week-day/_js.view/test.js b/1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/10-date/2-get-week-day/_js.view/test.js rename to 1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js diff --git a/1-js/05-data-types/10-date/2-get-week-day/solution.md b/1-js/05-data-types/11-date/2-get-week-day/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/2-get-week-day/solution.md rename to 1-js/05-data-types/11-date/2-get-week-day/solution.md diff --git a/1-js/05-data-types/10-date/2-get-week-day/task.md b/1-js/05-data-types/11-date/2-get-week-day/task.md similarity index 100% rename from 1-js/05-data-types/10-date/2-get-week-day/task.md rename to 1-js/05-data-types/11-date/2-get-week-day/task.md diff --git a/1-js/05-data-types/10-date/3-weekday/_js.view/solution.js b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/3-weekday/_js.view/solution.js rename to 1-js/05-data-types/11-date/3-weekday/_js.view/solution.js diff --git a/1-js/05-data-types/10-date/3-weekday/_js.view/test.js b/1-js/05-data-types/11-date/3-weekday/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/10-date/3-weekday/_js.view/test.js rename to 1-js/05-data-types/11-date/3-weekday/_js.view/test.js diff --git a/1-js/05-data-types/10-date/3-weekday/solution.md b/1-js/05-data-types/11-date/3-weekday/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/3-weekday/solution.md rename to 1-js/05-data-types/11-date/3-weekday/solution.md diff --git a/1-js/05-data-types/10-date/3-weekday/task.md b/1-js/05-data-types/11-date/3-weekday/task.md similarity index 100% rename from 1-js/05-data-types/10-date/3-weekday/task.md rename to 1-js/05-data-types/11-date/3-weekday/task.md diff --git a/1-js/05-data-types/10-date/4-get-date-ago/_js.view/solution.js b/1-js/05-data-types/11-date/4-get-date-ago/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/4-get-date-ago/_js.view/solution.js rename to 1-js/05-data-types/11-date/4-get-date-ago/_js.view/solution.js diff --git a/1-js/05-data-types/10-date/4-get-date-ago/_js.view/test.js b/1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/10-date/4-get-date-ago/_js.view/test.js rename to 1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js diff --git a/1-js/05-data-types/10-date/4-get-date-ago/solution.md b/1-js/05-data-types/11-date/4-get-date-ago/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/4-get-date-ago/solution.md rename to 1-js/05-data-types/11-date/4-get-date-ago/solution.md diff --git a/1-js/05-data-types/10-date/4-get-date-ago/task.md b/1-js/05-data-types/11-date/4-get-date-ago/task.md similarity index 100% rename from 1-js/05-data-types/10-date/4-get-date-ago/task.md rename to 1-js/05-data-types/11-date/4-get-date-ago/task.md diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/solution.js b/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/5-last-day-of-month/_js.view/solution.js rename to 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/test.js b/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/10-date/5-last-day-of-month/_js.view/test.js rename to 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/solution.md b/1-js/05-data-types/11-date/5-last-day-of-month/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/5-last-day-of-month/solution.md rename to 1-js/05-data-types/11-date/5-last-day-of-month/solution.md diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/task.md b/1-js/05-data-types/11-date/5-last-day-of-month/task.md similarity index 100% rename from 1-js/05-data-types/10-date/5-last-day-of-month/task.md rename to 1-js/05-data-types/11-date/5-last-day-of-month/task.md diff --git a/1-js/05-data-types/10-date/6-get-seconds-today/solution.md b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/6-get-seconds-today/solution.md rename to 1-js/05-data-types/11-date/6-get-seconds-today/solution.md diff --git a/1-js/05-data-types/10-date/6-get-seconds-today/task.md b/1-js/05-data-types/11-date/6-get-seconds-today/task.md similarity index 100% rename from 1-js/05-data-types/10-date/6-get-seconds-today/task.md rename to 1-js/05-data-types/11-date/6-get-seconds-today/task.md diff --git a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/solution.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/solution.md rename to 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md diff --git a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/task.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md similarity index 100% rename from 1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/task.md rename to 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md diff --git a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/solution.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/8-format-date-relative/_js.view/solution.js rename to 1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js diff --git a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/test.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/10-date/8-format-date-relative/_js.view/test.js rename to 1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js diff --git a/1-js/05-data-types/10-date/8-format-date-relative/solution.md b/1-js/05-data-types/11-date/8-format-date-relative/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/8-format-date-relative/solution.md rename to 1-js/05-data-types/11-date/8-format-date-relative/solution.md diff --git a/1-js/05-data-types/10-date/8-format-date-relative/task.md b/1-js/05-data-types/11-date/8-format-date-relative/task.md similarity index 100% rename from 1-js/05-data-types/10-date/8-format-date-relative/task.md rename to 1-js/05-data-types/11-date/8-format-date-relative/task.md diff --git a/1-js/05-data-types/10-date/article.md b/1-js/05-data-types/11-date/article.md similarity index 100% rename from 1-js/05-data-types/10-date/article.md rename to 1-js/05-data-types/11-date/article.md diff --git a/1-js/05-data-types/11-json/1-serialize-object/solution.md b/1-js/05-data-types/12-json/1-serialize-object/solution.md similarity index 100% rename from 1-js/05-data-types/11-json/1-serialize-object/solution.md rename to 1-js/05-data-types/12-json/1-serialize-object/solution.md diff --git a/1-js/05-data-types/11-json/1-serialize-object/task.md b/1-js/05-data-types/12-json/1-serialize-object/task.md similarity index 100% rename from 1-js/05-data-types/11-json/1-serialize-object/task.md rename to 1-js/05-data-types/12-json/1-serialize-object/task.md diff --git a/1-js/05-data-types/11-json/2-serialize-event-circular/solution.md b/1-js/05-data-types/12-json/2-serialize-event-circular/solution.md similarity index 100% rename from 1-js/05-data-types/11-json/2-serialize-event-circular/solution.md rename to 1-js/05-data-types/12-json/2-serialize-event-circular/solution.md diff --git a/1-js/05-data-types/11-json/2-serialize-event-circular/task.md b/1-js/05-data-types/12-json/2-serialize-event-circular/task.md similarity index 100% rename from 1-js/05-data-types/11-json/2-serialize-event-circular/task.md rename to 1-js/05-data-types/12-json/2-serialize-event-circular/task.md diff --git a/1-js/05-data-types/11-json/article.md b/1-js/05-data-types/12-json/article.md similarity index 100% rename from 1-js/05-data-types/11-json/article.md rename to 1-js/05-data-types/12-json/article.md diff --git a/1-js/05-data-types/11-json/json-meetup.svg b/1-js/05-data-types/12-json/json-meetup.svg similarity index 100% rename from 1-js/05-data-types/11-json/json-meetup.svg rename to 1-js/05-data-types/12-json/json-meetup.svg