This blog is excerpted from modern JS tutorials, links below
https://zh.javascript.info
Copy the code
This article extracts some of the key knowledge that I think you need to know, and is not a complete JS learning article, because of the length is very long, so many points released
Symbol.iterator
By creating an object ourselves, we can easily grasp the concept of iterability.
For example, we have an object that is not an array, but looks good for using for.. Of circulation.
For example, a range object represents a range of numbers:
let range = {
from: 1.to: 5
};
// We wish for... Of works like this:
// for(let num of range) ... Num = 1, 2, 3, 4, 5
Copy the code
To make range objects iterable (also let for.. Of can run.) We need to add a method to the object called symbol. iterator (a built-in Symbol designed to make the object iterable).
- when
for.. of
When the loop starts, it calls this method (or an error if it doesn’t find it). This method must return oneIterators– anext
Method object. - From then on,
for.. of
This applies only to the object being returned. - when
for.. of
When the loop wants to get the next value, it calls the object’snext()
Methods. next()
The format of the result returned by the method must be{done: Boolean, value: any}
whendone=true
Is, indicating the end of the iteration. Otherwisevalue
Is the next value.
Here is the full implementation of the annotated range:
let range = {
from: 1.to: 5
};
// 1. for.. The of call first calls this:
range[Symbol.iterator] = function() {
/ /... It returns an iterator object:
// 2. Of only works with this iterator, requiring it to provide the next value
return {
current: this.from,
last: this.to,
// 3. Next () in for.. Is called in each iteration of the loop
next() {
// 4. It will return {done:.. , value :... } format object
if (this.current <= this.last) {
return { done: false.value: this.current++ };
} else {
return { done: true}; }}}; };// Now it works!
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
Copy the code
Note the core functionality of the iterable: separation of concerns.
range
Without their ownnext()
Methods.- Instead, it is called
range[Symbol.iterator]()
Creates another object, the so-called “iterator” object, and itsnext
Values are generated for iteration.
Therefore, an iterator object is separate from the object it iterates over.
Technically, we can combine them and simplify the code by using range itself as an iterator.
Something like this:
let range = {
from: 1.to: 5[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false.value: this.current++ };
} else {
return { done: true}; }}};for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
Copy the code
Now range[symbol.iterator]() returns the range object itself: it includes the necessary next() methods and remembers the current iteration through this.current. It’s shorter, right? Yes. Sometimes that’s ok.
The downside is that it is now impossible to run two for’s on an object at the same time. Of loops: They will share the iteration state because there is only one iterator, the object itself. But two parallel for.. Of is very rare, even in asynchronous cases.
Infinite iterators
Infinite iterators are also possible. For example, if range is set to range.to = Infinity, range becomes an infinite iterator. Or we can create an iterable that generates an infinite sequence of pseudo-random numbers. It’s possible.
Next has no limitations, and it can return more and more values, which is normal.
Of course, iterating over for.. The of loop will not stop. But we can stop it by using break
Call iterators explicitly
To take a closer look at the underlying knowledge, let’s look at using iterators explicitly.
We will adopt the same approach as for.. Of iterates over the string in exactly the same way, but using a direct call. This code creates a string iterator and retrieves the value from it “manually”.
let str = "Hello";
/ / and the for... Do the same thing
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // Print characters one by one
}
Copy the code
Rarely do we need to do this, but more than for.. “Of” gives us more control. For example, we can break up an iteration: iterate part of it, then stop, do something else, and then resume the iteration.
Array.from
There is a global method array. from that can take the value of an iterable or array-like Array and get a “real” Array from it. Then we can call the array method on it.
Such as:
let arrayLike = {
0: "Hello".1: "World".length: 2
};
let arr = Array.from(arrayLike); / / (*)
alert(arr.pop()); // World (pop method works)
Copy the code
The array. from method on line (*) takes the object, checks that it is an iterable or array-like object, and then creates a new Array and copies all the elements of that object into the new Array.
The same is true for iterables:
// Suppose range comes from the example above
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (array toString)
Copy the code
The full syntax of array. from allows us to provide an optional “mapping” function:
Array.from(obj[, mapFn, thisArg])
Copy the code
The optional second argument, mapFn, can be a function that is applied to each element of the object before it is added to the array, and thisArg allows us to set this to the function.
Such as:
// Suppose range comes from the example above
// Square each number
let arr = Array.from(range, num= > num * num);
alert(arr); / / 1,4,9,16,25
Copy the code
Now we use array. from to convert a string to a single character Array:
let str = '𝒳 😂';
// Split STR into character arrays
let chars = Array.from(str);
alert(chars[0]); / / 𝒳
alert(chars[1]); / / 😂
alert(chars.length); / / 2
Copy the code
Unlike the str.split method, it relies on the iterable nature of strings. So, like for.. Surrogate pairs can be correctly handled, as can of. (Proxy pairs are utF-16 extensions.)
Technically, it does the same thing as the following code:
let str = '𝒳 😂';
let chars = []; // array. from executes the same loop internally
for (let char of str) {
chars.push(char);
}
alert(chars);
Copy the code
… But array. from is a lot leaner.
We can even create a surrogate aware slice method based on array. from:
function slice(str, start, end) {
return Array.from(str).slice(start, end).join(' ');
}
let str = '𝒳 😂 𩷶';
alert( slice(str, 1.3));/ / 😂 𩷶
// Native methods do not support identifying proxy pairs
alert( str.slice(1.3));// Garbled characters (the result of splicing two different UTF-16 extended characters)
Copy the code
Map and Set
Map
A Map is a collection of data items with keys, just like an Object. But the biggest difference is that maps allow any type of key.
Its methods and properties are as follows:
new Map()
Create a map.map.set(key, value)
Store values by key.map.get(key)
Returns the value based on the key, ifmap
There is no corresponding inkey
, the returnundefined
.map.has(key)
– ifkey
If it exists, returntrue
Otherwise returnfalse
.map.delete(key)
— Removes the value of the specified key.map.clear()
— Clear the map.map.size
— Returns the current number of elements.
Here’s an example:
let map = new Map(a); map.set('1'.'str1'); // String key
map.set(1.'num1'); / / the number keys
map.set(true.'bool1'); // Boolean key
// Remember the ordinary Object? It turns the key into a string
// Map keeps the key type, so these two results are different:
alert( map.get(1));// 'num1'
alert( map.get('1'));// 'str1'
alert( map.size ); / / 3
Copy the code
As we can see, keys, unlike objects, are not converted to strings. Keys can be of any type.
map[key]
Instead of usingMap
The right way
Although map[key] also works, for example we could set map[key] = 2, which would treat the map as a JavaScript plain object, and therefore imply all the corresponding restrictions (no object keys, etc.).
So we should use map methods: set, get, etc.
Maps can also use objects as keys.
Such as:
let john = { name: "John" };
// Store the number of visits per user
let visitsCountMap = new Map(a);// John is a key in Map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); / / 123
Copy the code
Using objects as keys is one of the most notable and important features of Map. For string keys, Object (plain Object) works fine, but not for Object keys.
Let’s try it:
let john = { name: "John" };
let visitsCountObj = {}; // Try using objects
visitsCountObj[john] = 123; // Try using the John object as the key
// Write it like this!
alert( visitsCountObj["[object Object]"]);/ / 123
Copy the code
Since visitsCountObj is an object, it will convert all keys like John to strings, so we get the string key “[Object object]”. This is obviously not what we want.
Map
How do you compare keys?
Map uses the SameValueZero algorithm to compare keys for equality. It’s the same thing as strictly equal ===, except NaN is treated as equal to NaN. So NaN can also be used as a key.
The algorithm cannot be changed or customized.
Chain calls
Each map.set call returns the map itself, so we can make “chained” calls:
map.set('1'.'str1')
.set(1.'num1')
.set(true.'bool1');
Copy the code
If you want to use loops in a map, you can use the following three methods:
map.keys()
Returns an iterable for keysmap.values()
Returns an iterable for valuesmap.entries()
— Iterate and return an iterable for entries[key, value]
.for.. of
This is what is used by default.
Such as:
let recipeMap = new Map([['cucumber'.500],
['tomatoes'.350],
['onion'.50]]);// Iterate over all keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// go through all the values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); / / 500, 350, 50
}
[key, value] [key, value]
for (let entry of recipeMap) { // Same as recipemap.entries ()
alert(entry); // cucumber,500 (and so on)
}
Copy the code
Use insert order
The order of iteration is the same as the order of insertion. Unlike ordinary objects, maps retain this order.
In addition, Map has a built-in forEach method, similar to Array:
// Run forEach on each key/value pair
recipeMap.forEach( (value, key, map) = > {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Copy the code
If you want to use loops in a map, you can use the following three methods:
map.keys()
Returns an iterable for keysmap.values()
Returns an iterable for valuesmap.entries()
— Iterate and return an iterable for entries[key, value]
.for.. of
This is what is used by default.
Such as:
let recipeMap = new Map([['cucumber'.500],
['tomatoes'.350],
['onion'.50]]);// Iterate over all keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// go through all the values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); / / 500, 350, 50
}
[key, value] [key, value]
for (let entry of recipeMap) { // Same as recipemap.entries ()
alert(entry); // cucumber,500 (and so on)
}
Copy the code
Use insert order
The order of iteration is the same as the order of insertion. Unlike ordinary objects, maps retain this order.
In addition, Map has a built-in forEach method, similar to Array:
// Run forEach on each key/value pair
recipeMap.forEach( (value, key, map) = > {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Copy the code
When a Map is created, we can pass in an array (or other iterable) with key-value pairs to initialize it, as follows:
// Array of key and value pairs
let map = new Map([['1'.'str1'],
[1.'num1'],
[true.'bool1']]); alert( map.get('1'));// str1
Copy the code
If we wanted to create a Map from an existing plain object, we could use the built-in object.entries (obj) method, which returns an array of key/value pairs for the object in exactly the format required for the Map.
So you can create a Map from an object like this:
let obj = {
name: "John".age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name'));// John
Copy the code
Here, Object.entries return array of key/value pairs: [[“name”,”John”], [“age”, 30]]. This is the format needed for a Map.
set
A Set is a special Set of types — a “Set of values” (without keys), each of which can occur only once.
Its main methods are as follows:
new Set(iterable)
Create aset
If provided with a可迭代
Object (usually an array) that copies values from the array toset
In the.set.add(value)
Add a value and return the set itselfset.delete(value)
— Delete the value ifvalue
Returns if it exists at the time of the method calltrue
Otherwise returnfalse
.set.has(value)
– ifvalue
In set, returntrue
Otherwise returnfalse
.set.clear()
— Clear set.set.size
— Returns the number of elements.
The main feature is that nothing changes when set.add(value) is called repeatedly with the same value. This is why each value in a Set appears only once.
For example, we have guests visiting and we want to remember each and every one of them. However, repeat visits by guests who have already visited should not result in duplicate records. Each visitor must be “counted” only once.
Set can help us solve this problem:
let set = new Set(a);let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits, some visitors visit several times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set only holds non-repeating values
alert( set.size ); / / 3
for (let user of set) {
alert(user.name); // John (then Pete and Mary)
}
Copy the code
An alternative to Set could be an array of users, using arr.find to check for duplicates each time a value is inserted. But performance is poor because the method iterates through the array to check each element. Uniqueness checking is better optimized inside Set.
We can use for.. Of or forEach to traverse Set:
let set = new Set(["oranges"."apples"."bananas"]);
for (let value of set) alert(value);
// Same as forEach:
set.forEach((value, valueAgain, set) = > {
alert(value);
});
Copy the code
Notice one interesting thing. ForEach’s callback takes three arguments: a value, then the same value valueAgain, and finally the target object. Yes, the same value appears twice in the argument.
ForEach’s callback takes three arguments to be compatible with Map. Of course, this does seem odd. But it’s helpful to easily substitute sets for maps in certain situations, and vice versa.
The methods used for iteration in Map are also supported in Set:
set.keys()
Returns an iterable object for values.set.values()
– andset.keys()
It’s the same. It’s for compatibilityMap
.set.entries()
— Iterate and return an iterable object for entries[value, value]
It also exists for compatibilityMap
.
conclusion
Map – is a collection of data items with keys.
Methods and attributes are as follows:
new Map([iterable])
Create a map, optionally with[key,value]
For the可迭代
(for example, an array) to initialize.map.set(key, value)
Store values by key.map.get(key)
Returns the value based on the key, ifmap
There is no corresponding inkey
, the returnundefined
.map.has(key)
– ifkey
If it exists, returntrue
Otherwise returnfalse
.map.delete(key)
— Removes the value of the specified key.map.clear()
— Clear the map.map.size
— Returns the current number of elements.
Differences with common Object:
- Any key or object can be a key.
- There are other convenient methods such as
size
Properties.
Set – is a Set of unique values.
Methods and properties:
new Set([iterable])
Create a set, optionally with可迭代
(for example, an array) to initialize.set.add(value)
— Add a value (ifvalue
If it exists, no changes are made), return set itself.set.delete(value)
— Delete the value ifvalue
Returns if it exists at the time of the method calltrue
Otherwise returnfalse
.set.has(value)
– ifvalue
In set, returntrue
Otherwise returnfalse
.set.clear()
— Clear set.set.size
Number of elements.
In maps and sets, iteration is always done in the order in which values are inserted, so we can’t say that collections are unordered, but we can’t reorder elements or get them by their numbers.
practice
Anagrams are words that have the same number of letters but in a different order.
Such as:
nap - pan
ear - are - era
cheaters - hectares - teachers
Copy the code
Write a function aclean(arr) that returns an array of anagrams that have been cleared.
Such as:
let arr = ["nap"."teachers"."cheaters"."PAN"."ear"."era"."hectares"];
alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era"
Copy the code
For all anagram groups, one of the words should be kept, but it doesn’t matter which one.
Open the sandbox with tests.
The solution
To find all anagrams, let’s break each word into letters and sort it. When the letters are sorted, all the puzzles are the same.
Such as:
nap, pan -> anp
ear, era, are -> aer
cheaters, hectares, teachers -> aceehrst
...
Copy the code
We will use the alphabetized variants of the words as the map keys, each of which stores only one value:
function aclean(arr) {
let map = new Map(a);for (let word of arr) {
// Split the word into letters, sort the letters, and then join back
let sorted = word.toLowerCase().split(' ').sort().join(' '); / / (*)
map.set(sorted, word);
}
return Array.from(map.values());
}
let arr = ["nap"."teachers"."cheaters"."PAN"."ear"."era"."hectares"];
alert( aclean(arr) );
Copy the code
Alphabetical sorting is done in a chain call on the (*) line.
For convenience, let’s break it up into multiple lines:
let sorted = word // PAN
.toLowerCase() // pan
.split(' ') // ['p','a','n']
.sort() // ['a','n','p']
.join(' '); // anp
Copy the code
Two different words ‘PAN’ and ‘nap’ get the same alphabetic form ‘anp’.
The next line is to put words into map:
map.set(sorted, word);
Copy the code
If we encounter the same alphabetical word again, it will overwrite the previous value in the map with the same key. Therefore, each letter form has at most one word. (and the last value of each letter)
Finally, array.from (map.values()) iterates the values of the map (we don’t need the resulting key) into an Array and returns the Array.
Here, we can also use plain Objects instead of maps, because keys are strings.
Here’s the solution:
function aclean(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let sorted = arr[i].toLowerCase().split("").sort().join("");
obj[sorted] = arr[i];
}
return Object.values(obj);
}
let arr = ["nap"."teachers"."cheaters"."PAN"."ear"."era"."hectares"];
alert( aclean(arr) );
Copy the code
Use the test functionality of the sandbox to open the solution.
We expect to get an array using map.keys() and then process it using specific methods such as.push and so on.
But it doesn’t work:
let map = new Map(a); map.set("name"."John");
let keys = map.keys();
// Error: keys.push is not a function
keys.push("more");
Copy the code
Why is that? How should we modify the code to make keys.push work?
The solution
This is because map.keys() returns an iterable instead of an array.
We can convert it to an Array using the array. from method:
let map = new Map(a); map.set("name"."John");
let keys = Array.from(map.keys());
keys.push("more");
alert(keys); // name, more
Copy the code
WeakMap and WeakSet
WeakMap
The first difference between WeakMap and Map is that the key of WeakMap must be an object, not the original value:
let weakMap = new WeakMap(a);let obj = {};
weakMap.set(obj, "ok"); // Work correctly (object as key)
// Cannot use strings as keys
weakMap.set("test"."Whoops"); // Error because "test" is not an object
Copy the code
Now, if we use an object as a key in weakMap and there are no other references to this object — the object will be automatically cleared from memory (and map).
let john = { name: "John" };
let weakMap = new WeakMap(a); weakMap.set(john,"...");
john = null; // Override the reference
// John was removed from memory!
Copy the code
Compared to the normal Map example above, now if John exists only as a WeakMap key — it will be automatically removed from the Map (and memory).
WeakMap does not support iteration and keys(), values() and entries() methods. So there is no way to get all the keys or values of WeakMap.
WeakMap only has the following methods:
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
Why is there such a limitation? It’s the technology. If an object loses all other references (as John did in the example above), it is automatically reclaimed by the garbage collection mechanism. But from a technical point of view, it is impossible to know exactly when it will be recycled.
These are all determined by the JavaScript engine. A JavaScript engine might choose to do a memory cleanup now, and if a lot of deletions are happening now, the JavaScript engine might choose to wait and do the cleanup later. Therefore, technically, the current number of WeakMap elements is unknown. The JavaScript engine may have cleaned up some of the junk, may not have cleaned up some of the junk. Therefore, methods to access all keys/values of WeakMap are not currently supported.
So where do we need such data structures?
The main application scenario of WeakMap is the storage of additional data.
If we are working with an object that “belongs” to another code, or perhaps a third-party library, and want to store some data related to it, then the data should live and die with that object — WeakMap is what we need.
We put the data into WeakMap and use the object as the key of the data, so when the object is collected by the garbage collection mechanism, the data will also be cleared automatically.
weakMap.set(john, "secret documents");
// If John disappears, Secret Documents will be cleared automatically
Copy the code
Let’s look at an example.
For example, we have code for handling user access counts. The collected information is stored in a map: a user object is the key, and the number of times it is accessed is a value. When a user leaves (the user object will be collected by the garbage collection mechanism), we no longer need his number of visits.
Here is an example of a counting function that uses Map:
/ / 📁 visitsCount. Js
let visitsCountMap = new Map(a);// map: user => visits count
// Increment the number of user visits
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
Copy the code
Here is the rest of the code, and possibly other code that uses it:
/ / 📁 main. Js
let john = { name: "John" };
countUser(john); // count his visits
// After a while, John left
john = null;
Copy the code
Now the John object should be garbage collected, but it’s still in memory because it’s a key in visitsCountMap.
When we remove the user, we need to clean up visitsCountMap, otherwise it will grow infinitely in memory. In a complex architecture, this cleanup can be an onerous task.
We can avoid such problems by using WeakMap:
/ / 📁 visitsCount. Js
let visitsCountMap = new WeakMap(a);// weakmap: user => visits count
// Increment the number of user visits
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
Copy the code
Now we don’t need to clean up visitsCountMap. When a John object becomes inaccessible, even if it is a key in a WeakMap, it is removed from memory along with the information it corresponds to as a key in a WeakMap.
WeakSet
WeakSet has similar performance:
- with
Set
Similar, but we can only askWeakSet
Add objects (not raw values). - Objects can stay in a set only if they can be accessed somewhere else.
- with
Set
The same,WeakSet
supportadd
.has
和delete
Method, but not supportedsize
和keys()
, and cannot be iterated.
As weak as it is, it also serves as extra storage space. But not to arbitrary data, but to yes/no facts. WeakSet elements may represent some information about that object.
For example, we can add users to WeakSet to keep track of users who have visited our site:
let visitedSet = new WeakSet(a);let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); John paid us a visit
visitedSet.add(pete); // Then Pete
visitedSet.add(john); // John visits again
// visitedSet now has two users
// Check whether John has visited.
alert(visitedSet.has(john)); // true
// Check whether Mary has called.
alert(visitedSet.has(mary)); // false
john = null;
// visitedSet will be automatically cleaned
Copy the code
The most obvious limitation of WeakMap and WeakSet is that they cannot iterate and cannot capture all the current content. That may be inconvenient, but it doesn’t prevent WeakMap/WeakSet from doing its main job — being managing/storing “extra” object data elsewhere.
Keys, values, entries
For ordinary objects, the following methods are available:
- Keys (obj) – Returns an array containing all the keys of the Object.
- Object.values(obj) – Returns an array containing all the values of the Object.
- Object.entries(obj) – Returns an array containing all the [key, value] key-value pairs of this Object.
… But notice the difference (e.g. with map) :
Map | Object | |
---|---|---|
Call syntax | map.keys() |
Object.keys(obj) Rather thanobj.keys() |
The return value | Item can be iterative | A “real” array |
The first difference is that the call syntax we use for objects is object.keys (obj), not obj.keys().
Why is that? The main reason is flexibility. Remember that in JavaScript, objects are the foundation of all complex structures. Thus, we might have a self-created object, such as data, that implements its own data.values() method. Meanwhile, we can still call the object.values (data) method on it.
The second difference is that the Object.* method returns a “real” array Object, not just an iterable item.
Object. The keys/symbol attribute values/entries will be ignored
As for.. As with the in loop, these methods ignore the use of Symbol(…) As a key property.
Usually this is convenient. But, if we also want the Symbol type of key, so there is a separate methods Object. GetOwnPropertySymbols, it returns an array containing only Symbol type of key. Alternatively, there is a method reflect.ownKeys (obj), which returns all keys.
Object conversion
Object lacks many of the methods in which arrays exist, such as map and filter.
If we wanted to apply them, we could use Object.entries and then Object.fromEntries:
- use
Object.entries(obj)
从obj
Gets an array of key/value pairs. - Use the array method on the array, for example
map
. - Used with the result array
Object.fromEntries(array)
Method to turn the result back into an object.
For example, we have an object with prices and want to double them:
let prices = {
banana: 1.orange: 2.meat: 4};let doublePrices = Object.fromEntries(
// Convert to an array, use the Map method, and then return to an object through fromEntries
Object.entries(prices).map(([key, value]) = > [key, value * 2])); alert(doublePrices.meat);/ / 8
Copy the code
Deconstruction assignment
Array structure
Here is an example of deconstructing an array into a variable:
// We have an array of first and last names
let arr = ["Ilya"."Kantor"]
// Destruct the assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;
alert(firstName); // Ilya
alert(surname); // Kantor
Copy the code
Now we can operate on these variables instead of the original array elements.
When combined with the split function (or other functions that return an array), it looks even more elegant:
let [firstName, surname] = "Ilya Kantor".split(' ');
Copy the code
Unwanted elements in the array can also be discarded by adding additional commas:
// The second element is not required
let [firstName, , title] = ["Julius"."Caesar"."Consul"."of the Roman Republic"];
alert( title ); // Consul
Copy the code
In the above code, the second element of the array is skipped, the third element is assigned to the title variable, and the rest of the array is skipped (because there is no corresponding variable for them here).
Any iterable can be on the right-hand side of the equals sign
… In fact, we can use it with any iterable, not just arrays:
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1.2.3]);
Copy the code
A typical technique for swapping variable values:
let guest = "Jane";
let admin = "Pete";
// Switch values: let guest=Pete, admin=Jane
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane (successful swap!)
Copy the code
Rest parameters
If we don’t just want to get the first value, we also want to collect all subsequent elements — we can use three dots “…” Add one more parameter to receive the “remaining” element:
let [name1, name2, ...rest] = ["Julius"."Caesar"."Consul"."of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// Notice that 'rest' is of type array
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); / / 2
Copy the code
The rest value is an array of the remaining elements in the array. It is not necessary to use the variable name rest, we can use any other variable name, just make sure it is preceded by three dots and at the last parameter position of the deconstructed assignment.
The default value
If the number of variables in an assignment statement is greater than the number of actual elements in the array, the assignment will not fail. Variables that are not assigned are considered undefined:
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
Copy the code
If we want a “default” value for an unassigned variable, we can use = to provide:
/ / the default value
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (value from array)
alert(surname); // Anonymous (default is used)
Copy the code
Object to deconstruct
Destructuring assignments also applies to objects.
The basic syntax is:
let {var1, var2} = {var1:... .var2:... }Copy the code
We have an existing object on the right side of the equals sign, and we want to break it into variables. The left side of the equals sign contains a “pattern” for the corresponding attribute of the object. In the simple case, the left-hand side of the equals sign is {… } list of variable names in.
Here’s an example:
let options = {
title: "Menu".width: 100.height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); / / 100
alert(height); / / 200
Copy the code
If we want to assign a property to a variable of another name, such as assigning the options.width property to w, we can specify this using a colon:
let options = {
title: "Menu".width: 100.height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); / / 100
alert(h); / / 200
Copy the code
For properties that may be missing, we can use “=” to set the default value, as shown below:
let options = {
title: "Menu"
};
let {width = 100, height = 200, title} = options;
alert(title); // Menu
alert(width); / / 100
alert(height); / / 200
Copy the code
We can also combine the colon and equal sign:
let options = {
title: "Menu"
};
let {width: w = 100.height: h = 200, title} = options;
alert(title); // Menu
alert(w); / / 100
alert(h); / / 200
Copy the code
What if the object has more attributes than the number of variables we provide? Can we just take some of these properties and assign the rest somewhere else?
We can use residual patterns, just as we do with arrays. Some older browsers do not support this feature (for example, populating it with Babel), but it can be used in modern browsers.
It looks something like this:
let options = {
title: "Menu".height: 200.width: 100
};
// title = property named title
// rest = objects with remaining attributes
let{title, ... rest} = options;// now title="Menu", rest={height: 200, width: 100}
alert(rest.height); / / 200
alert(rest.width); / / 100
Copy the code
Do not uselet
When the trap of
In the above example, variables are declared correctly in assignment: let {… } = {… }. Of course, we could have used existing variables instead of let, but there is a catch.
The following code does not work:
let title, width, height;
// There is an error in this line
{title, width, height} = {title: "Menu".width: 200.height: 100};
Copy the code
The problem is that JavaScript puts the {… } as a code block. Such code blocks can be used to group statements as follows:
{
// a block of code
let message = "Hello";
// ...
alert( message );
}
Copy the code
So, JavaScript here assumes that we have a block of code, and that’s why the error is reported. We need to deconstruct it.
To tell JavaScript that this is not a code block, we can put the entire assignment expression in parentheses (…). Wrap:
let title, width, height;
// Now it's ready
({title, width, height} = {title: "Menu".width: 200.height: 100});
alert( title ); // Menu
Copy the code
Nested deconstruction
If an object or array is nested with other objects and arrays, we can use a more complex pattern on the left side of the equal sign to extract deeper data.
In the code below, the size property of options is another object, and the items property is another array. The pattern to the left of the medium sign in an assignment statement has the same structure to extract a value from:
let options = {
size: {
width: 100.height: 200
},
items: ["Cake"."Donut"].extra: true
};
// For clarity, destruct assignment statements are written as multiple lines
let {
size: { // Assign size here
width,
height
},
items: [item1, item2], // Assign items here
title = "Menu" // Does not exist in the object (use default values)
} = options;
alert(title); // Menu
alert(width); / / 100
alert(height); / / 200
alert(item1); // Cake
alert(item2); // Donut
Copy the code
All attributes of the options object, except the extra attribute, which does not exist on the left side of the equal sign, are assigned to the corresponding variable:
Finally, we have the width, height, Item1, Item2, and title variables with default values.
Notice that there are no corresponding variables for size and items because we are taking their contents.
Date and time
new Date(year, month, date, hours, minutes, seconds, ms)
Copy the code
Creates a date with the given component in the current time zone. Only the first two parameters are required.
year
Must be a four-digit number:2013
It’s legal,98
It’s not legal.month
Count from0
(January) start, arrive11
(December) end.date
The value is a specific day in the current month. If the value is missing, it is the default value1
.- if
hours/minutes/seconds/ms
If no, the default values are used0
.
Such as:
new Date(2011.0.1.0.0.0.0); // 1 Jan 2011, 00:00:00
new Date(2011.0.1); // The time, minute, and second values are 0 by default
Copy the code
The time measure is accurate to a minimum of 1 millisecond (1/1000 second) :
let date = new Date(2011.0.1.2.3.4.567);
alert( date ); / / 1.01.2011 02:03:04. 567
Copy the code
There are several ways to access information such as year and month from Date objects:
-
getFullYear()
Get the year (4 digits)
-
getMonth()
Gets the month, from 0 to 11.
-
getDate()
Get the date of the month, from 1 to 31, which may seem confusing.
-
GetHours (), getMinutes(), getSeconds(), getMilliseconds()
Gets the corresponding time component.
notgetYear()
, butgetFullYear()
Many JavaScript engines implement a nonstandardized method called getYear(). This method is not recommended. It may sometimes return 2-bit year information. Never use it. To get the year, use getFullYear().
Automatic calibration is a very convenient feature of Date objects. We can set values out of range, and it will automatically calibrate.
Here’s an example:
let date = new Date(2013.0.32); // 32 Jan 2013 ? ! ?
alert(date); / /... Is 1 st Feb 2013.
Copy the code
If we just want to measure the time interval, we don’t need the Date object.
There is a special method called date.now () that returns the current timestamp.
It is equivalent to new Date().getTime(), but it does not create an intermediate Date object. So it’s faster, and there’s no extra stress on garbage disposal.
This approach is often adopted for convenience or performance reasons, such as using JavaScript to write games or other special application scenarios.
So this might be better:
let start = Date.now(); // Time stamp from 1 Jan 1970 to now
// do the job
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = Date.now(); / / finish
alert( `The loop took ${end - start} ms` ); // Subtract the timestamp, not the date
Copy the code
JSON
JSON (JavaScript Object Notation) is a common format for representing values and objects. It is described in the RFC 4627 standard. It was originally created for JavaScript, but many other programming languages also have libraries for handling it. Therefore, when the client side uses JavaScript and the server side is written in languages such as Ruby/PHP/Java, using JSON makes it easy to exchange data.
JavaScript provides the following methods:
JSON.stringify
Convert the object to JSON.JSON.parse
Convert JSON back to an object.
For example, here we have json.stringify a student object:
let student = {
name: 'John'.age: 30.isAdmin: false.courses: ['html'.'css'.'js'].wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // we've got a string!
alert(json);
/ * JSON encoding object: {" name ":" John ", "age" : 30, "isAdmin" : false, "net" : [" HTML ", "CSS", "js"], "the wife" : null} * /
Copy the code
The json.stringify (student) method takes the object and converts it to a string.
The resulting JSON string is an object called JSON-encoded or serialized or stringified or marshalled. We are now ready to send it wired or put it into a regular data store.
Note that jSON-encoded objects differ from object literals in several important ways:
- Use double quotation marks for strings. There are no single or backquotes in JSON. so
'John'
Is converted to"John"
. - Object property names are also quoted. It’s mandatory. so
age:30
Be converted to"age":30
.
Json.stringify can also be applied to primitive datatype.
JSON supports the following data types:
- Objects
{... }
- Arrays
[...].
- The Primitives:
- Strings,
- Numbers,
- boolean values
true/false
. null
.
Such as:
// Numbers are still numbers in JSON
alert( JSON.stringify(1))/ / 1
// A string is still a string in JSON, but is enclosed by double quotes
alert( JSON.stringify('test'))// "test"
alert( JSON.stringify(true));// true
alert( JSON.stringify([1.2.3]));/ / [1, 2, 3]
Copy the code
JSON is a language-neutral pure data specification, so some javascripts specific object properties are skipped by json.stringify.
- Function properties (methods).
- Symbol Property of type.
- storage
undefined
Properties.
let user = {
sayHi() { / / is ignored
alert("Hello"); },Symbol("id")]: 123./ / is ignored
something: undefined / / is ignored
};
alert( JSON.stringify(user) ); // {} (empty object)
Copy the code
Usually that’s good. If this is not the way we want, we will soon see how to customize the transformation.
Best of all, nested object transformations are supported and can be converted automatically.
Such as:
let meetup = {
title: "Conference".room: {
number: 23.participants: ["john"."ann"]}}; alert(JSON.stringify(meetup) );
/ * the deconstruction were stringized {" title ":" Conference ", "room" : {" number ": 23," participants ": [" John", "Ann"]},} * /
Copy the code
Important limitation: No circular references.
Such as:
let room = {
number: 23
};
let meetup = {
title: "Conference".participants: ["john"."ann"]}; meetup.place = room;// Meetup references room
room.occupiedBy = meetup; // Room quotes Meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
Copy the code
The full syntax of json.stringify is:
let json = JSON.stringify(value[, replacer, space])
Copy the code
-
value
The value to encode.
-
replacer
The array of attributes or mapping functions to encode function(key, value).
-
space
The number of Spaces used for formatting
In most cases, json.stringify is used only with the first parameter. However, if we need to fine-tune the substitution process, such as filtering out circular references, we can use the second parameter of json.stringify.
If we pass it an array of properties, only those properties will be encoded.
Such as:
let room = {
number: 23
};
let meetup = {
title: "Conference".participants: [{name: "John"}, {name: "Alice"}].place: room // Meetup references room
};
room.occupiedBy = meetup; // Room quotes Meetup
alert( JSON.stringify(meetup, ['title'.'participants']));// {"title":"Conference","participants":[{},{}]}
Copy the code
Maybe we’re being too strict here. The property list is applied to the entire object structure. So the participants is empty, because name is not in the list.
Let’s include all the attributes except room. OccupiedBy, which causes circular references:
let room = {
number: 23
};
let meetup = {
title: "Conference".participants: [{name: "John"}, {name: "Alice"}].place: room // Meetup references room
};
room.occupiedBy = meetup; // Room quotes Meetup
alert( JSON.stringify(meetup, ['title'.'participants'.'place'.'name'.'number']));/* { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */
Copy the code
Now, everything except occupiedBy is serialized. But the list of attributes is too long.
Fortunately, we can use a function instead of an array as a replacer.
This function is called for each (key,value) pair and returns the “replaced” value, which replaces the original value. If the value is skipped, undefined.
In our example, we can return value as-is for everything except occupiedBy. For occupiedBy, the following code returns undefined:
let room = {
number: 23
};
let meetup = {
title: "Conference".participants: [{name: "John"}, {name: "Alice"}].place: room // Meetup references room
};
room.occupiedBy = meetup; // Room quotes Meetup
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy')?undefined : value;
}));
/* key:value pairs that come to replacer: : [object Object] title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 */
Copy the code
Note that the replacer function retrieves each key/value pair, including nested objects and array items. It is applied recursively. The value of this in replacer is the object containing the current property.
The first call is special. It is made using a special “wrapper object “: {“”: Meetup}. In other words, the key of the first (key, value) pair is empty, and the value is the entire target object. This is why the first line in the example above is “:[object object]”.
The idea is to give the replacer as much functionality as possible: it has the opportunity to analyze and replace/skip the entire object if necessary.
Like toString for string conversion, objects can also provide toJSON methods for JSON conversion. If available, json.stringify will call it automatically.
Such as:
let room = {
number: 23
};
let meetup = {
title: "Conference".date: new Date(Date.UTC(2017.0.1)),
room
};
alert( JSON.stringify(meetup) );
/ * {" title ":" Conference ", "date" : "the 2017-01-01 T00:00:00. 000 z," / / "room" (1) : 23} {" number ": / / (2)} * /
Copy the code
Here we can see that date (1) becomes a string. This is because all dates have a built-in toJSON method to return strings of this type.
Now let’s add a custom toJSON for our room object:
let room = {
number: 23.toJSON() {
return this.number; }};let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); / / 23
alert( JSON.stringify(meetup) );
/* { "title":"Conference", "room": 23 } */
Copy the code
As we’ve seen, toJSON can be used either to call json.stringify (room) directly or when room is nested in another encoded object.
To decode JSON strings, we need another method, json.parse.
Grammar:
let value = JSON.parse(str, [reviver]);
Copy the code
-
str
The JSON string to parse.
-
reviver
Optional function(key,value), which will be called for each (key,value) pair and can convert the value.
Such as:
// String arrays
let numbers = "[0, 1, 2, 3]." ";
numbers = JSON.parse(numbers);
alert( numbers[1]);/ / 1
Copy the code
For nested objects:
let userData = '{" name ":" John ", "age" : 35, "isAdmin" : false, "friends" :,1,2,3 [0]}';
let user = JSON.parse(userData);
alert( user.friends[1]);/ / 1
Copy the code
JSON can be quite complex, and objects and arrays can contain other objects and arrays. However, they must follow the same JSON format.
Here are some typical errors when writing JSON by hand (sometimes we have to write it for debugging purposes) :
let json = '{name: "John", // Error: attribute name without double quotation marks "surname": 'Smith', // error: value with single quotation marks (double quotation marks must be used) 'isAdmin': "Birthday ": new Date(2000, 2,3), "birthday": new Date(2000, 2,3), "birthday": new Date(2000, 2,3), "friends": [0,1,2,3];
Copy the code
In addition, JSON does not support annotations. Adding comments to JSON is invalid.
There is another format called JSON5, which allows unquoted keys, comments, and so on. But this is a separate library, not in the language specification.
The regular JSON format is strict, not because its developers are lazy, but because it is easy, reliable, and fast to implement parsing algorithms.
Imagine that we get a String-like Meetup object from the server.
It looks something like this:
// title: (meetup title), date: (meetup date)
let str = '{" title ":" Conference ", "date" : "the 2017-11-30 T12:00:00. 000 z"}';
Copy the code
… Now we need to deserialize it and convert it back into a JavaScript object.
Let’s do this by calling json.parse:
let str = '{" title ":" Conference ", "date" : "the 2017-11-30 T12:00:00. 000 z"}';
let meetup = JSON.parse(str);
alert( meetup.date.getDate() ); // Error!
Copy the code
Ah! An error!
The value of meetup.date is a string, not a date object. How does json. parse know to convert a string to Date?
Parse lets pass the reviver function to json. parse as the second argument, which returns all values “as is”, but date becomes date:
let str = '{" title ":" Conference ", "date" : "the 2017-11-30 T12:00:00. 000 z"}';
let meetup = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( meetup.date.getDate() ); // Now it works!
Copy the code
By the way, this also applies to nested objects:
let schedule = `{ "meetups": [{" title ":" Conference ", "date" : "the 2017-11-30 T12:00:00. 000 z"}, {" title ":" Birthday ", "date" : "the 2017-04-18 T12:00:00. 000 z"}]} `;
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( schedule.meetups[1].date.getDate() ); // It's working!
Copy the code
In the case of simple circular references, we can exclude serialized offending attributes by name.
However, sometimes we can’t just use the name, because it can be used in a circular reference as well as in a regular attribute. Therefore, we can check properties by their values.
Write the replacer function to remove the property referencing Meetup and serialize all the other properties:
let room = {
number: 23
};
let meetup = {
title: "Conference".occupiedBy: [{name: "John"}, {name: "Alice"}].place: room
};
// Circular reference
room.occupiedBy = meetup;
meetup.self = meetup;
alert( JSON.stringify(meetup, function replacer(key, value) {
/* your code */
}));
/ * the result should be: {" title ":" Conference ", "occupiedBy" : [{" name ":" John "}, {" name ":" Alice "}], "place" : {" number ": 23}} * /
Copy the code
The solution
let room = {
number: 23
};
let meetup = {
title: "Conference".occupiedBy: [{name: "John"}, {name: "Alice"}].place: room
};
room.occupiedBy = meetup;
meetup.self = meetup;
alert( JSON.stringify(meetup, function replacer(key, value) {
return(key ! ="" && value == meetup) ? undefined : value;
}));
/* { "title":"Conference", "occupiedBy":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */
Copy the code
Here we also need to determine key==”” to exclude the case where value is meetup on the first call.