MEMEPh. ideas that are worth sharing...

ES7, ES8, ES9, ES10 new features

Foreword



Since ECMAScript 2016 (ES7), version releases have become more frequent, and a new version is released every year. Fortunately, there are not many updates in each version. This article will elaborate on these new features, as much as possible related to old knowledge, to help You quickly get started with these features.

 

What's New in ES7


1. Array.prototype.includes() method

In ES6 we have String.prototype.includes() which can query whether a given string contains a character, and in ES7, we can also use Array.prototype.includes method in an array to determine whether an array contains a specified character The value, as appropriate, returns true if included, false otherwise.

const arr = [1, 3, 5, 2, '8', NaN, -0]
arr.includes(1) // true
arr.includes(1, 2) // false The second parameter of this method indicates the starting position of the search, the default is 0
arr.includes('1') // false
arr.includes(NaN) // true
arr.includes(+0) // true


Before ES7, if you wanted to determine whether an array contained an element, there were two methods as follows, but neither was as intuitive as includes:

indexOf()

The indexOf() method returns the first index in the array at which a given element can be found, or -1 if it does not exist.

if (arr.indexOf(el) !== -1) {
  // ...
}

However, this method has two disadvantages. First, it is not semantic enough. It is necessary to find the first occurrence position of the parameter value, so it is not intuitive enough to compare whether it is not equal to -1. Second, it uses the strict equality operator (===) internally for judgment, which will lead to misjudgment of NaN.

[NaN].indexOf(NaN)// -1

The find method of an array instance is used to find the first eligible array member. In addition, both methods can find NaN, which makes up for the lack of indexOf method of array.

[1, 4, -5, 10].find((n) => n < 0) // -5
[1, 5, 10, 15].findIndex(function(value) {
  return value > 9;
}) // 2
[NaN].findIndex(y => Object.is(NaN, y)) // 0
Array.prototype.includes() support:

 

2. Exponentiation operator**

The exponent operator was introduced in ES7, with the equivalent of Math.pow()

console.log(2**10);// output 1024zhicc
console.log(Math.pow(2, 10)) // output 1024


Support for exponentiation operators:

 

ES8 new features


1. Async/Await

We all know that using Promises can solve the problem of callback hell well, but if the processing flow is more complicated, then the whole code will be filled with then, the semantics are not obvious, and the code cannot well represent the execution flow. Is there a better way to do that? What about a more elegant asynchronous way of Promises?

Suppose there is such a usage scenario: you need to request a link first, and then request another resource linked to b after returning the information. The following code shows the use of fetch to achieve such a requirement, fetch is defined in the window object, and it returns a Promise object

fetch('https://rendc.com/')
  .then(response => {
    console.log(response)
    return fetch('https://memeph.com/')
  })
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })


Although the above code can achieve this requirement, the semantics are not obvious, and the code cannot well represent the execution flow. For this reason, ES8 introduced async/await, which is a major improvement in JavaScript asynchronous programming, providing the ability to use synchronous code to access resources asynchronously without blocking the main thread, and making the code logic clearer.

async function foo () {
  try {
    let response1 = await fetch('https://blog.csdn.net/')
    console.log(response1)
    let response2 = await fetch('https://juejin.im/')
    console.log(response2)
  } catch (err) {
    console.error(err)
  }
}
foo()


Through the above code, you will find that the entire asynchronous processing logic is implemented using synchronous code, and it also supports try catch to catch exceptions. This feels like writing synchronous code, so it is very consistent with human linear thinking. It should be emphasized that await cannot be used independently of async . The await must be followed by a Promise object. If not, it will be automatically wrapped into a Promise object.

According to the MDN definition, async is a function that executes asynchronously and implicitly returns a Promise as a result .

async function foo () {
return 'boating in the waves'
}
foo().then(val => {
console.log(val) // boating in the waves
})


In the above code, we can see that calling the foo function declared by async returns a Promise object, which is equivalent to the following code:

async function foo () {
return Promise.resolve('boating in the waves')
}
foo().then(val => {
console.log(val) // boating in the waves
})

Async/Await support:

 

2.Object.values(),Object.entries()

ES5 introduces the Object.keys method, which returns an array whose members are the keys of all enumerable properties of the parameter object itself (excluding inherited). ES8 introduced Object.values ​​and Object.entries, which are matched with Object.keys, as a supplementary means of traversing an object for the for...of loop.

The Object.values ​​method returns an array whose members are the keys of all enumerable properties of the parameter object itself (excluding inherited).

const obj = { foo: 'bar', baz: 42 };
Object.values(obj) // ["bar", 42]
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj) // ["b", "c", "a"]

It should be noted that if the attribute name is a numerical attribute, it is traversed according to the numerical value, from small to large , so the returned order is b, c, a.

The Object.entries() method returns an array of key-value pairs of all enumerable properties of the parameter object itself (excluding inherited). This feature will be mentioned later when we introduce Object.fromEntries() of ES10.

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' };
Object.entries(obj); // [['1', 'yyy'], ['3', 'zzz'], ['10': 'xxx']]

Object.values() is compatible with Object.entries(). The following takes Object.values() as an example:

 

3. String padding

In ES8, String has added two instance functions String.prototype.padStart and String.prototype.padEnd, allowing empty strings or other strings to be added to the beginning or end of the original string. Let's look at the syntax first:

String.padStart(targetLength,[padString])

targetLength (required): The target length to which the current string needs to be padded. If this value is less than the length of the current string, the current string itself is returned.
padString (optional): The padding string. If the string is too long and the length of the padded string exceeds the target length, only the leftmost part will be kept, and other parts will be truncated. The default value of this parameter is " ".

'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'

Sometimes we often need to format when dealing with dates and amounts, and this feature comes in handy:

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

String padding support:

 

4.Object.getOwnPropertyDescriptors()

ES5's Object.getOwnPropertyDescriptor() method returns the descriptor of an object property. ES8 introduces the Object.getOwnPropertyDescriptors() method, which returns the description object of all its own properties (non-inherited properties) of the specified object.

const obj = {
name: 'boating in the waves',
get bar () {
return 'abc'
} }
}
console.log(Object.getOwnPropertyDescriptors(obj))

The following results are obtained: The purpose of this method is to solve the problem that Object.assign() cannot correctly copy the get and set properties. Let's look at an example:

const source = {
set foo (value) {
console.log(value)
},
get bar () {
return 'boating in the waves'
} }
}
const target1 = {}
Object.assign(target1, source)
console.log(Object.getOwnPropertyDescriptor(target1, 'foo'))

Returns the following results:


In the above code, the value of the foo property of the source object is an assignment function. The Object.assign method copies this property to the target1 object, and as a result, the value of the property becomes undefined. This is because the Object.assign method always copies the value of a property, not the assign method or getter method behind it .

At this time, the Object.getOwnPropertyDescriptors() method cooperates with the Object.defineProperties() method to achieve correct copying.

const source = {
set foo (value) {
console.log(value)
},
get bar () {
return 'boating in the waves'
} }
}
const target2 = {}
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source))
console.log(Object.getOwnPropertyDescriptor(target2, 'foo'))

Returns the following results:

Object.getOwnPropertyDescriptors() support:

 

What's New in ES9


1. for await of

The for of method can traverse synchronous iterator data with the Symbol.iterator interface, but cannot traverse asynchronous iterators.
ES9's new for await of can be used to traverse a data structure with the Symbol.asyncIterator method, that is, an asynchronous iterator, and will wait for the state of the previous member to change before traversing to the next member, which is equivalent to the internal async function. await. Now we have three asynchronous tasks, and we want to output the results in sequence. How to do it?

// for of traversal
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
} }
}
test()

Got the following result:

The above code confirms that the for of method cannot traverse the asynchronous iterator, and the result obtained is not what we expected, so for await of comes on stage!

function Gen (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve(time)
    }, time)
  })
}
async function test () {
  let arr = [Gen(2000), Gen(100), Gen(3000)]
  for await (let item of arr) {
    console.log(Date.now(), item)
  }
}
test()
// 1575536194608 2000
// 1575536194608 100
// 1575536195608 3000

When using for await of traversal, it will wait for the state of the previous Promise object to change before traversing to the next member.

Support for asynchronous iterators:

 

2. Object Rest Spread

One of the most interesting features added in ES6 is the spread operator. Not only can you use it to replace the cancat() and slice() methods to make array operations (copy, merge) easier, but the spread operator is also useful in situations where arrays must be split as function parameters .

const arr1 = [10, 20, 30];
const copy = [...arr1]; // copy
console.log(copy); // [10, 20, 30]
const arr2 = [40, 50];
const merge = [...arr1, ...arr2]; // merge
console.log(merge); // [10, 20, 30, 40, 50]
console.log(Math.max(...arr)); // 30 dismantling

ES9 further extends this syntax by adding extended properties to object literals. He can copy the properties of one object to another object, refer to the following situations:

const input = {
  a: 1,
  b: 2,
  c: 1
}
const output = {
  ...input,
  c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}

The above code can add the data of the input object to the output object. It should be noted that if the same attribute name exists, only the last one will take effect .

const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
input.a='boating in the waves'
console.log(input,output) // {a: "boating in the waves", b: 2} {a: 1, b: 2, c: 3}

In the above example, the value in the input object is modified, but the output does not change, indicating that the spread operator copies an object (like this obj2 = {...obj1}), and the implementation is just a shallow copy of an object . It is worth noting that if the value of the property is an object, the reference to the object will be copied:

const obj = {x: {y: 10}};
const copy1 = {...obj};
const copy2 = {...obj};
obj.x.y='boating in the waves'
console.log(copy1,copy2) // x: {y: "boating in waves"} x: {y: "boating in waves"}
console.log(copy1.x === copy2.x); // → true

copy1.x and copy2.x are references to the same object, so they are strictly equal.

Let's take another look at the example of Object rest:

const input = {
a: 1,
b: 2,
c: 3
}
let { a, ...rest } = input
console. log(a, rest) // 1 {b: 2, c: 3}

When the key-value of the object is uncertain, assign the required key to a variable, and use a variable to converge other optional key data, which was impossible before. Note that the rest property must always appear at the end of the object , otherwise an error will be thrown.

Rest is compatible with Spread. The following uses Spread as an example:

 

3. Promise.prototype.finally()

The Promise.prototype.finally() method returns a Promise. At the end of the promise execution, whether the result is fulfilled or rejected, after executing then() and catch(), the callback function specified by finally will be executed.

fetch('https://www.rendc.com')
  .then((response) => {
    console.log(response.status);
  })
  .catch((error) => { 
    console.log(error);
  })
  .finally(() => { 
    document.querySelector('#spinner').style.display = 'none';
  });

The finally() method comes in handy when you need to do some cleanup after the operation, regardless of whether the operation was successful or not. This provides a way to specify the code that needs to be executed after the promise is executed, whether the result is fulfilled or rejected, avoiding the situation where the same statement needs to be written once in then() and catch() .

 

4. New Regular Expression Features

ES9 adds four new features to regular expressions that further improve JavaScript's string processing capabilities. These features are as follows:

(1)s(dotAll)flag

In regular expressions, the dot (.) is a special character that represents any single character, with two exceptions. One is a four-byte UTF-16 character, which can be solved with the u modifier; the other is a line terminator, such as a newline (\n) or a carriage return (\r), which can be resolved by ES9's s( dotAll)flag, adding s to the original regular expression means:

console.log(/foo.bar/.test('foo\nbar')) // false
console.log(/foo.bar/s.test('foo\nbar')) // true

So how to judge whether the current regex uses the dotAll mode?

const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's');`.
console.log(re.test('foo\nbar')) // true
console.log(re.dotAll) // true
console.log(re.flags) // 's'

 

(2) Named capturing groups

In some regular expression patterns, matching using numbers can be confusing. For example, use the regular expression /(\d{4})-(\d{2})-(\d{2})/ to match dates. Because the date notation in American English is different from the date notation in British English, it can be difficult to distinguish which group represents the day and which group represents the month:

const re = /(\d{4})-(\d{2})-(\d{2})/;
const match= re.exec('2019-01-01');
console.log(match[0]);    // → 2019-01-01
console.log(match[1]);    // → 2019
console.log(match[2]);    // → 01
console.log(match[3]);    // → 01

ES9 introduced named capturing groups, which allow assigning a name to each group match, making it easier to read code and easier to reference.

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec('2019-01-01');
console.log(match.groups);          // → {year: "2019", month: "01", day: "01"}
console.log(match.groups.year);     // → 2019
console.log(match.groups.month);    // → 01
console.log(match.groups.day);      // → 01

In the above code, the "named capturing group" is inside the parentheses, and "question mark + angle brackets + group name" (?) is added to the head of the pattern, and then the group name can be referenced on the groups attribute of the result returned by the exec method.

Named capture groups can also be used in the replace() method, for example to convert dates to the US MM-DD-YYYY format:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const usDate = '2018-04-30'.replace(re, '$<month>-$<day>-$<year>')
console.log(usDate) // 04-30-2018

 

(3) Lookbehind backward assertion

The regular expressions of the JavaScript language only support look-ahead assertions, not look-behind assertions. A look-ahead assertion can be simply understood as "first encounter a condition, and then judge whether it is satisfied later", as shown in the following example:

let test = 'hello world'
console.log(test.match(/hello(?=\sworld)/))
// ["hello", index: 0, input: "hello world", groups: undefined]

But sometimes we want to judge that the front is the hello of the world, this code cannot be realized. This lookbehind assertion is supported in ES9:

let test = 'world hello'
console.log(test.match(/(?<=world\s)hello/))
// ["hello", index: 6, input: "world hello", groups: undefined]

(?<...) is the symbol of the lookbehind assertion, (?..) is the symbol of the lookahead assertion , and then combined with = (equal to), ! (not equal), \1 (capture match).

 

(4) Unicode attribute escape

ES2018 introduces a new class notation \p{...} and \P{...} that allow regular expressions to match all characters that conform to a certain property of Unicode. For example, you can use \p{Number} to match all Unicode numbers, for example, assuming you want to match a string of Unicode characters ㉛:

const str = '㉛';
console.log(/\d/u.test(str));    // → false
console.log(/\p{Number}/u.test(str));     // → true

Likewise, you can use \p{Alphabetic} to match all Unicode word characters:

const str = 'ض';
console.log(/\p{Alphabetic}/u.test(str));     // → true
// the \w shorthand cannot match ض
console.log(/\w/u.test(str));    // → false

There is also a negative Unicode property escape template \P{...}

console.log(/\P{Number}/u.test('㉛'));    // → false
console.log(/\P{Number}/u.test('ض'));    // → true
console.log(/\P{Alphabetic}/u.test('㉛'));    // → true
console.log(/\P{Alphabetic}/u.test('ض'));    // → false

In addition to letters and numbers, some other properties can be used in Unicode property escapes.

Support for the above features

 

What's New in ES10


1. Array.prototype.flat()

Multidimensional arrays are a common data format, especially when doing data retrieval. Flattening multidimensional arrays is a common requirement. Usually we can do it, but it's not elegant enough.

The flat() method recursively traverses the array to a specified depth, and returns all elements combined with the elements in the traversed subarrays into a new array.

newArray = arr.flat(depth) // depth specifies the depth of the structure to extract the nested array, the default value is 1

Next we look at two examples:

const numbers1 = [1, 2, [3, 4, [5, 6]]]
console.log(numbers1.flat())// [1, 2, 3, 4, [5, 6]]
const numbers2 = [1, 2, [3, 4, [5, 6]]]
console.log(numbers2.flat(2))// [1, 2, 3, 4, 5, 6]

The above two examples show that the parameter of flat is not set, and the default value is 1, which means that only the first level is flattened; when the parameter of flat is greater than or equal to 2, the return value is [1, 2, 3, 4, 5, 6] .

 

2.Array.prototype.flatMap()

With the flat method, there is naturally the Array.prototype.flatMap method. The flatMap() method first maps each element using a mapping function, and then compresses the result into a new array. It can also be seen from the name of the method that it contains two functions, one is map and the other is flat (depth 1).

let arr = [1, 2, 3]
console.log(arr.map(item => [item * 2]).flat()) // [2, 4, 6]
console.log(arr.flatMap(item => [item * 2])) // [2, 4, 6]

In fact, flatMap combines the operations of map and flat, so it can only level one layer .

Array.prototype.flatmap support:

 

3. Object.fromEntries()

Object.fromEntries This new API implements the opposite of Object.entries. This makes it easy to get objects based on their entries.

const object = { x: 23, y:24 };
const entries = Object.entries(object); // [['x', 23], ['y', 24]]
const result = Object.fromEntries(entries); // { x: 23, y: 24 }

ES2017 introduced Object.entries, which can convert objects into arrays, so that objects can use many built-in methods in the array prototype, such as map, filter, reduce, for example, we want to extract all values ​​greater than or equal to the following object obj 21 key-value pairs, how to operate?

// Before ES10
const obj = {
a: 21,
b: 22,
c: 23
}
console.log(Object.entries(obj)) // [['a',21],["b", 22],["c", 23]]
let arr = Object.entries(obj).filter(([a, b]) => b > 21) // [["b", 22],["c", 23]]
let obj1 = {}
for (let [name, age] of arr) {
obj1[name] = age
}
console.log(obj1) // {b: 22, c: 23}

In the above example, the array arr is obtained. If you want to convert it into an object again, you need to manually write some code to deal with it, but with Object.fromEntries() it is easy to achieve

// Implemented with Object.fromEntries()
const obj = {
a: 21,
b: 22,
c: 23
}
let res = Object.fromEntries(Object.entries(obj).filter(([a, b]) => b > 21))
console.log(111, res) // {b: 22, c: 23}

 

4. String.trimStart and String.trimEnd

Remove leading and trailing spaces, which we used to do with regular expressions, but now ES10 has two new features that make this even easier!

The trimStart() method removes whitespace from the beginning of the string, trimLeft() is an alias for this method.

let str = 'frontend artisan'
console.log(str.length) // 6
str = str.trimStart()
console.log(str.length) // 5
let str1 = str.trim() // remove leading and trailing spaces
console.log(str1.length) // 4
str.replace(/^\s+/g, '') // You can also use regular implementation to delete spaces at the beginning

The trimEnd() method removes whitespace characters from the right end of a string, trimRight is an alias for trimEnd.

let str = 'boating in the waves'
console.log(str.length) // 6
str = str.trimEnd()
console.log(str.length) // 5
let str1 = str.trim() //Clear spaces before and after
console.log(str1.length) // 4
str.replace(/\s+$/g, '') // You can also use regular expressions to remove whitespace characters from the right end

String.trimStart and String.trimEnd have the same compatibility. The following figure takes trimStart as an example:

 

5. String.prototype.matchAll

If a regular expression has multiple matches in the string, now generally use the g modifier or y modifier, and take them out one by one in the loop.

function collectGroup1 (regExp, str) {
  const matches = []
  while (true) {
    const match = regExp.exec(str)
    if (match === null) break
    matches.push(match[1])
  }
  return matches
}
console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))
// [ 'foo', 'bar', 'baz' ]

It's worth noting that without the modifier /g, .exec() only returns the first match. Now through ES9's String.prototype.matchAll method, all matches can be retrieved at once.

function collectGroup1 (regExp, str) {
  let results = []
  for (const match of str.matchAll(regExp)) {
    results.push(match[1])
  }
  return results
}
console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))
// ["foo", "bar", "baz"]

In the above code, since string.matchAll(regex) returns a traverser, it can be retrieved with a for...of loop.

String.prototype.matchAll support:

 

6. try…catch

In ES10, the parameter in the try-catch statement became optional. Previously, when we wrote the catch statement, we had to pass an exception parameter. This means that even if we don't need to use this exception parameter in the catch at all, it must be passed in

// Before ES10
try {
// tryCode
} catch (err) {
// catchCode
}

Here err is a required parameter, which can be omitted in ES10:

// ES10
try {
  console.log('Foobar')
} catch {
  console.error('Bar')
}

The support of try...catch:

 

7. BigInt

All numbers in JavaScript are stored as 64-bit floating point numbers, which imposes two major limitations on the representation of numbers. First, the precision of numerical values ​​can only reach 53 binary digits (equivalent to 16 decimal digits). Integers larger than this range cannot be accurately represented by JavaScript, which makes JavaScript unsuitable for accurate calculations in science and finance. The second is a value greater than or equal to 2 to the power of 1024, which cannot be represented by JavaScript and will return Infinity.

// Values ​​over 53 binary digits cannot maintain precision
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
// Values ​​exceeding 2 to the power of 1024 cannot be represented
Math.pow(2, 1024) // Infinity

Now ES10 introduces a new data type BigInt (big integer) to solve this problem. BigInt is only used to represent integers, there is no limit on the number of digits, and integers of any number of digits can be represented exactly.

Creating a value of type BigInt is also as simple as appending n to the number. For example, 123 becomes 123n. You can also use the global method BigInt(value) to convert, the input parameter value is a number or a number string.

const aNumber = 111;
const aBigInt = BigInt(aNumber);
aBigInt === 111n // true
typeof aBigInt === 'bigint' // true
typeof 111 // "number"
typeof 111n // "bigint"

If you count BigInt, the number of primitive types in JavaScript goes from 6 to 7.

 

8. Symbol.prototype.description

We know that the description of Symbol is only stored in the internal [[Description]], not directly exposed to the outside world, we can only read this property when calling toString() of Symbol:

Symbol('desc').description;  // "desc"
Symbol('').description;      // ""
Symbol().description;        // undefined

Support for Symbol.prototype.description:

 

9. Function.prototype.toString()

In ES2019, Function.toString() has changed. When this method was executed before, the resulting string was blank-free. And now, the resulting string looks like the original source code:

function sum(a, b) {
  return a + b;
}
console.log(sum.toString());
// function sum(a, b) {
//  return a + b;
// }

Function.prototype.toString() support: