30 ES6-ES12 development skills you need to know!

It’s another big meal, and another 10,000-character long article. I reorganized the common new features of ES6——ES12. Many features are still very practical in development. I hope it can help you a little bit! There is a lot of content in the article, it is recommended to bookmark it for study first!

ECMAScript  is a scripting programming language standardized by Ecma International through ECMA-262, which is called JavaScript. Simply put, ECMAScript is the standard and specification of JavaScript, and JavaScript is the implementation and extension of the ECMAScript standard.

Since 2015, ECMAScript has released the following versions:

release time official name version name initials
2015 ECMAScript2015 ECMAScript6 ES2015、ES6
2016 ECMAScript2016 ECMAScript7 ES2016、ES7
2017 ECMAScript2017 ECMAScript8 ES2017、ES8
2018 ECMAScript2018 ECMAScript9 ES2018、ES9
2019 ECMAScript2019 ECMAScript10 ES2019、ES10
2020 ECMAScript2020 ECMAScript11 ES2020、ES11
2021 ECMAScript2021 ECMAScript12 ES2021、ES12

Let's take a look at the usage skills of each version of ECMAScript. 

ECMAScript.png

 **Note: **Some knowledge points have been introduced in previous articles, so I won’t repeat them in this article, and the corresponding article links have been attached in the article.

1. ES6 new features (2015)

The update of ES6 is mainly reflected in the following aspects:

  • Expressions: variable declaration, destructuring assignment
  • Built-in objects: string extension, value extension, object extension, array extension, function extension, regular extension, Symbol, Set, Map, Proxy, Reflect
  • Statements and operations: Class, Module, Iterator
  • Asynchronous programming: Promise, Generator, Async.

Here are some commonly used new features. There are also some features, which have been introduced in previous articles, so I won’t say more here, just link them directly:

1. let and const

In ES6, let and const keywords are added, where let is mainly used to declare variables, and const is usually used to declare constants. Compared with the var keyword, let and const have the following characteristics:

characteristic was let const
variable hoisting ✔️ × ×
global variable ✔️ × ×
repeat statement ✔️ × ×
reassignment ✔️ ✔️ ×
temporary dead zone × ✔️ ✔️
block scope × ✔️ ✔️
Just declare without initialization ✔️ ✔️ ×

Here are four of them:

(1) Reassignment

Variables declared with the const keyword are "unmodifiable". In fact, what const guarantees is not that the value of the variable cannot be changed, but that the memory address pointed to by the variable cannot be changed. For basic types of data (numbers, strings, Boolean values), its value is stored at the memory address pointed to by the variable, so it is equivalent to a constant. But for reference-type data (mainly objects and arrays), the variable points to the memory address of the data, and only a pointer is saved. const can only guarantee that the pointer remains unchanged. As for the data structure it points to, it is uncontrollable.

(2) Block-level scope

Before the introduction of let and const, there was no such thing as block-level scope, which caused many problems, such as inner variables covering outer variables with the same name:

var a = 1;
if (true) {
  var a = 2;
}

console.log(a);   // 输出结果:2 

Loop variables are leaked as global variables:

var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 输出结果:1  2  3
}

console.log(i); // 输出结果:3

However, the variables defined by let and const have block-level scope, so the above problems will not occur:

let a = 1;
if (true) {
  let a = 2;
}

console.log(a); // 输出结果:1

const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 输出结果:1  2  3
}

console.log(i); // Uncaught ReferenceError: i is not defined

(3) Variable promotion

We know that variable promotion existed before ES6. The so-called variable promotion means that variables can be used before declaration:

console.log(a); // 输出结果:undefined
var a = 1;

The essence of variable promotion is that the JavaScript engine will compile and analyze the code before executing the code. At this stage, the detected variables and function declarations will be added to the memory named Lexical Environment in the JavaScript engine, and an initialization value of undefined will be assigned . Then enter the code execution phase. So the declared variables and functions are already known to the JS engine before the code is executed.

This phenomenon is not in line with our intuition, so in ES6, the let and const keywords limit variable promotion. After the variable defined by let is added to the Lexical Environment, it will no longer be initialized to undefined. The JS engine will only execute it when it reaches Initialization occurs at lexical declaration and assignment. In the time span between variable creation and actual initialization, they cannot be accessed or used. ES6 calls it a temporary dead zone:

// 暂时性死区 开始
a = "hello";     //  Uncaught ReferenceError: Cannot access 'a' before initialization

let a;   
//  暂时性死区 结束
console.log(a);  // undefined

(4) Duplicate statement

Prior to ES6, variables declared with the var keyword had no restrictions on repeated declarations of variables within a scope, and even variables with the same name as parameters could be declared. The following two functions will not report an error:

function funcA() {
  var a = 1;
  var a = 2;
}

function funcB(args) {
  var args = 1; 
}

And let fixes this imprecise design:

function funcA() {
  let a = 1;
  let a = 2;  // Uncaught SyntaxError: Identifier 'a' has already been declared
}

function funcB(args) {
  let args = 1;  // Uncaught SyntaxError: Identifier 'args' has already been declared
}

Now we have completely abandoned var in our project, and use let to define variables and const to define constants. The following rules are enabled in ESlint:

"no-var": 0;

2. Destructuring assignment

ES6 also introduces the concept of destructuring assignment, which follows "pattern matching", that is, as long as the patterns on both sides of the equal sign are equal, the variable on the left will be assigned the corresponding value. Different types of data are deconstructed in different ways. Let's take a look at the deconstruction methods of different types of data.

Usually in development, I mainly use the destructuring assignment of objects , such as deconstructing the porps value in React, etc., and use the destructuring assignment to obtain the value passed from the parent component; useState in React Hooks uses the destructuring assignment of the array;

(1) Array deconstruction

All data structures with the Iterator interface can be deconstructed and assigned in the form of an array.

const [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz) // 输出结果:1  2  3

Here, ES6 implements the structure of the array, and assigns the variables foo, bar, and baz in sequence. Array destructuring assigns values ​​to variables by position.

Arrays can also be destructured incompletely, only partially:

const [x, y] = [1, 2, 3];   // 提取前两个值
const [, y, z] = [1, 2, 3]  // 提取后两个值
const [x, , z] = [1, 2, 3]  // 提取第一三个值

If there is no value in the corresponding position during deconstruction, the variable will be assigned as undefined:

const [x, y, z] = [1, 2]; 
console.log(z)  // 输出结果:undefined

Array destructuring assignments can use the rest operator to capture leftover items:

const [x, ...y] = [1, 2, 3];   
console.log(x);  // 输出结果:1
console.log(y);  // 输出结果:[2, 3]

It also supports the use of default values ​​when deconstructing, and the default values ​​will only be used when the corresponding value is undefined:

const [x, y, z = 3] = [1, 2]; 
console.log(z)  // 输出结果:3

(2) Object deconstruction

The essence of deconstructing and assigning an object is to first find the attribute with the same name, and then assign it to the corresponding variable:

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar); // 输出结果:aaa  bbb

It should be noted that in JavaScript, the properties of an object are not ordered. Therefore, when deconstructing and assigning, the variable must have the same name as the attribute to get the value.

The destructuring assignment of objects also supports default values. When the defined variable does not exist in the object, its default value will take effect:

let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb', baz: null };
console.log(foo, bar, baz); // 输出结果:aaa  bbb  null

let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar, baz); // 输出结果:aaa  bbb  ccc

It can be seen that only when the defined variable is strictly ===undefined, its default value will take effect.

In addition, we also need to pay attention that we cannot assign values ​​to declared variables, because when the let, const, and var keywords are missing, {baz} will be interpreted as a code block and cause a syntax error, so the following code will Error:

let baz;
{ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' };

The above problem can be solved by wrapping the entire destructuring assignment statement in parentheses:

let baz;
({ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' });
console.log(baz)

In object destructuring assignment, you can assign a method of an existing object to a variable, for example:

let { log, sin, cos } = Math;
log(12)  // 输出结果:2.4849066497880004
sin(1)   // 输出结果:0.8414709848078965
cos(1)   // 输出结果:0.5403023058681398

(3) Other destructuring assignments

For the remaining destructuring assignments, I have seldom used them in projects so far, so let’s take a brief look.

  • string deconstruction

String deconstruction rules: As long as the value on the right side of the equal sign is not an object or an array, it will be converted into an array-like object first, and then destructured:

const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e)  // 输出结果:h e l l o

Array-like objects have a length property, so this property can be destructured and assigned:

let {length} = 'hello';    // 输出结果: 5

Since a string is a constant, we usually know what its value is, so destructuring assignment of variables is rarely used.

  • Numeric and boolean destructuring assignments

When destructuring numbers and booleans, they are first converted to objects and then the destructuring syntax is applied:

let {toString: s} = 123;
s === Number.prototype.toString // 输出结果:true

let {toString: s} = true;
s === Boolean.prototype.toString // 输出结果:true

Note that null and undefined cannot be converted into objects, so if these two values ​​are on the right, an error will be reported.

  • Function parameter destructuring assignment

The function parameter is an array on the surface, and the moment the parameter is passed in, it will be destructured into x and y.

function add([x, y]){
  return x + y;
}
add([1, 2]);   // 3

In addition, we can also destructure the return value of the function:

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

3. Template strings

In the traditional JavaScript language, output templates often use the form of string splicing, which is quite cumbersome to write. ES6 introduces the concept of template strings to solve the above problems.

A template string is an enhanced version of a string, identified by backticks ``, it can be used to define a single-line string, or to define a multi-line string, or to embed variables in a string.

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 字符串中调用函数
` ${fn()} 

In normal development, in addition to the application in the above code, template strings are used in many places, such as splicing a DOM string, defining DOM structures in Emotion/styled, etc., template strings will be used. However, there will be no code hints for defining DOM elements in template strings.

There are a few things to keep in mind when using template strings:

  • If you use backticks in a string, you need to use \ to escape;
  • If there are spaces and indents in the multiline string, they will be preserved in the output;
  • To embed variables in the template string, you need to write the variable name in ${};
  • Arbitrary expressions can be placed in the template string, operations can also be performed, properties of objects can be referenced, and functions can even be called;
  • If the variable in the template string is not declared, an error will be reported.

4. Function default parameters

Before ES6, functions did not support default parameters. ES6 implemented support for this, and the default value will only be triggered when no parameters are passed in:

function getPoint(x = 0, y = 0) {
  console.log(x, y);
}

getPoint(1, 2);   // 1  2
getPoint()        // 0  0 
getPoint(1)       // 1  0

When using function default values, you need to pay attention to the following points:

(1) Function length attribute value

The function length attribute is usually used to indicate the number of function parameters. When the default value of the function is introduced, length indicates the number of ordinary parameters before the first parameter with a default value:

const funcA = function(x, y) {};
console.log(funcA.length);  // 输出结果:2 

const funcB = function(x, y = 1) {};
console.log(funcB.length);  // 输出结果:1

const funcC = function(x = 1, y) {};
console.log(funcC.length);  // 输出结果 0 

(2) Parameter scope

When the default value is set for the parameters of the function, the parameters will form an independent scope when they are initialized, and the scope will be resolved after the initialization is completed:

let x = 1;

function func(x, y = x) {
  console.log(y);
}

func(2);  

This will eventually print 2. When the function is called, the parameters x and y will form an independent scope, so the y in the parameter will be equal to the x in the first parameter, instead of 1 as defined above.

5. Arrow functions

Arrow functions were introduced in ES6 to simplify the definition of functions:

const counter = (x, y) => x + y;

Compared with ordinary functions, arrow functions have the following characteristics:

(1) more concise

  • If there are no parameters, just write an empty parenthesis directly
  • If there is only one parameter, the parentheses around the parameter can be omitted
  • If there are multiple parameters, separate them with commas
  • If the return value of the function body is only one sentence, the curly braces can be omitted
// 1. 不传入参数
const funcA = () => console.log('funcA');
// 等价于
const funcA = function() {
  console.log('funcA');
} 

// 2. 传入参数
const funcB = (x, y) => x + y;
// 等价于
const funcB = function(x, y) {
  return x + y;
} 

// 3. 单个参数的简化
const funcC = (x) => x;
// 对于单个参数,可以去掉 (),简化为
const funcC = x => x;
// 等价于
const funcC = function(x) {
  return x;
}

// 4. 上述代码函数体只有单条语句,如果有多条,需要使用 {}
const funcD = (x, y) => { console.log(x, y); return x + y; }
// 等价于
const funcD = function(x, y) {
  console.log(x, y);
  return x + y;
}

(2) Do not bind this

The arrow function does not create its own this, so it does not have its own this, it only inherits this in the upper layer of its own scope. So the point of this in the arrow function has been determined when it is defined, and will not change afterwards.

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

The method b of the object obj is defined using an arrow function. The this in this function always points to the this in the global execution environment where it is defined. Even if this function is called as a method of the object obj, this still points to the Window object. It should be noted that the curly braces defining the object {}cannot form a separate execution environment, and it is still in the global execution environment.

Similarly, using methods such as call(), apply(), bind(), etc. cannot change the direction of this in the arrow function:

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(3) Cannot be used as a constructor

The execution steps of the constructor new operator are as follows:

  1. create an object
  2. Assign the scope of the constructor to the new object (that is, point the object's __proto__ attribute to the constructor's prototype attribute)
  3. Point to the code in the constructor, this in the constructor points to the object (that is, add properties and methods to this object)
  4. return new object

In fact, the second step is to point this in the function to the object. However, since the arrow function does not have its own this, and this points to the outer execution environment, and cannot change the point, it cannot be used as a constructor.

(4) Do not bind arguments

Arrow functions do not have their own arguments object. Accessing arguments in an arrow function actually gets the arguments value of its outer function.

6. Spread operator

Spread operator: ... is like the inverse operation of the rest parameter, converting an array into a sequence of parameters separated by commas, and unpacking the array.

The spread operator has the following uses :

(1) Convert the array into a sequence of parameters separated by commas:

function  test(a,b,c){
    console.log(a); // 1
    console.log(b); // 2
    console.log(c); // 3
}

var arr = [1, 2, 3];
test(...arr);

(2) Concatenate one array to another:

var arr1 = [1, 2, 3,4];
var arr2 = [...arr1, 4, 5, 6];
console.log(arr2);  // [1, 2, 3, 4, 4, 5, 6]

(3) Convert the string to a comma-separated array:

var str='JavaScript';
var arr= [...str];
console.log(arr); // ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

7. Symbol

ES6 introduces a new basic data type Symbol, which represents a unique value. It is a data type similar to a string, and its characteristics are as follows:

  • The value of Symbol is unique and used to resolve naming conflicts
  • Symbol values ​​cannot be operated on with other types of data
  • The object properties defined by Symbol cannot use for...inthe traversal loop, but can be used Reflect.ownKeys to get all the key names of the object
let s1 = Symbol();
console.log(typeof s1); // "symbol"

let s2 = Symbol('hello');
let s3 = Symbol('hello');
console.log(s2 === s3); // false

Based on the above characteristics, the Symbol attribute type is more suitable for two types of scenarios: constant value and object attribute .

(1) Avoid duplication of constant values

The getValue function will execute the corresponding code logic according to the incoming string parameter key:

function getValue(key) {
  switch(key){
    case 'A':
      ...
    case 'B':
      ...
  }
}
getValue('B');

This code is very unfriendly to the caller, because the code uses a magic string (Magic string, refers to a specific string or value that appears multiple times in the code and forms a strong coupling with the code) , resulting in the need to view the function code to find the optional value of the parameter key when calling the getValue function. So you can declare the value of the parameter key as a constant:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
}
function getValue(key) {
  switch(key){
    case KEY.alibaba:
      ...
    case KEY.baidu:
      ...
  }
}
getValue(KEY.baidu);

But this is not perfect. Suppose you want to add a key to the KEY constant. According to the corresponding rules, it is very likely that the value will be repeated:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
  tencent: 'B'
}

Here comes the problem:

getValue(KEY.baidu) // 等同于 getValue(KEY.tencent)

Therefore, it is more suitable to use Symbol in this scenario. You don’t need to care about the value itself, only the uniqueness of the value:

const KEY = {
  alibaba: Symbol(),
  baidu: Symbol(),
  tencent: Symbol()
}

(2) Avoid object property overwriting

The function fn needs to add a temporary attribute user to the object parameter passed in, but this attribute may already exist in the object parameter, and if it is directly assigned, the previous value will be overwritten. At this point you can use Symbol to avoid this problem. Create a variable of Symbol data type, and then assign and read the variable as an attribute of an object parameter, so as to avoid overwriting:

function fn(o) { // {user: {id: xx, name: yy}}
  const s = Symbol()
  o[s] = 'zzz'
}

8. Set

ES6 provides a new data structure Set (collection). It is similar to an array, but the values ​​of its members are unique, and the collection implements the iterator interface, so it can be traversed using the spread operator and for...of.

Set properties and methods:

properties and methods overview
size Returns the number of elements in the collection
add Add a new element and return the current collection
delete remove element, return boolean
has Checks whether a collection contains an element, returns a Boolean value
clear Empty collection, return undefined
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1,2,3,1,2,3]);
//返回集合的元素个数
console.log(s1.size);       // 3
//添加新元素
console.log(s1.add(4));     // {1,2,3,4}
//删除元素
console.log(s1.delete(1));  //true
//检测是否存在某个值
console.log(s1.has(2));     // true
//清空集合
console.log(s1.clear());    //undefined

Due to the uniqueness of the elements in the collection, in practical applications, set can be used to implement deduplication of arrays:

let arr = [1,2,3,2,1]
Array.from(new Set(arr))  // {1, 2, 3}

The Array.form() method is used here to convert the array collection into an array.

You can use set to find the intersection and union of two arrays:

// 模拟求交集 
let intersection = new Set([...set1].filter(x => set2.has(x)));

// 模拟求差集
let difference = new Set([...set1].filter(x => !set2.has(x)));

The following methods can be used to convert between arrays and collections:

// Set集合转化为数组
const arr = [...mySet]
const arr = Array.from(mySet)

// 数组转化为Set集合
const mySet = new Set(arr)

9. Map

ES6 provides a Map data structure, which is similar to an object and a collection of key-value teams, but its key-value range is not limited to strings, and can be any type of value (including objects). That is to say, the Object structure provides For the correspondence of "string-value", the Map structure provides the correspondence of "value-value", which is a more complete implementation of the Hash structure. If you need a "key-value pair" data structure, Map is more suitable than Object. Map also implements the iterator interface, so it can be traversed using the spread operator and for...of.

Map properties and methods:

properties and methods overview
size Returns the number of elements in the Map
set Add a new element and return the current Map
get Returns the key value of the key object
has Check if an element is contained in the Map and return a Boolean value
clear Clear the Map and return undefined
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
 ['name', 'hello'],
]);
//获取映射元素的个数
console.log(m2.size);          // 1
//添加映射值
console.log(m2.set('age', 6)); // {"name" => "hello", "age" => 6}
//获取映射值
console.log(m2.get('age'));    // 6
//检测是否有该映射
console.log(m2.has('age'));    // true
//清除
console.log(m2.clear());       // undefined

It should be noted that only references to the same object are considered as the same key by the Map structure:

let map = new Map(); 
map.set(['a'], 555); 
map.get(['a']) // undefined

The set and get methods of the above code are for the same key on the surface, but in fact these are two values, and the memory addresses are different, so the get method cannot read the key, so it will return undefined.

It can be seen from the above that the key of Map is actually bound to the memory address, as long as the memory address is different, it is regarded as two keys. This solves the problem of attribute collision (clash) with the same name. When expanding the library, if you use the object as the key name, you don't have to worry about your own attribute having the same name as the original attribute.

If the key of the Map is a value of a simple type (Number, String, Boolean), the Map treats two values ​​as a key as long as they are strictly equal, including 0 and -0. Also, while NaN is not strictly equal to itself, Map treats it as the same key.

let map = new Map(); 
map.set(NaN, 123); 
map.get(NaN) // 123 
map.set(-0, 123); 
map.get(+0) // 123 

10. Modularity

The ES Module, a modular development specification, was introduced for the first time in ES6, allowing Javascript to support native modular development for the first time. ES Module treats a file as a module, and each module has its own independent scope, so how to connect each module? The core point is the import and export of modules.

(1) export export module

  • Normal export:
// 方式一
export var first = 'test';
export function func() {
    return true;
}

// 方式二
var first = 'test';
var second = 'test';
function func() {
    return true;
}
export {first, second, func};
  • as keyword:
var first = 'test';
export {first as second};

The as keyword can rename exposed variables or methods, and the same variable can be exposed multiple times after renaming.

  • export default

export default will export the default output, that is, the user does not need to know the name of the output in the module, and can specify any name for it when importing.

// 导出
export default function () {
  console.log('foo');
}
// 导入
import customName from './export-default';

Note:  Braces are not required when importing a default module. Exporting a default variable or method can have a name, but it is not valid externally. export default can only be used once.

(2) import import module

  • Normal import:
import {firstName, lastName, year} from './profile';

The location of the imported module can be a relative path or an absolute path, and .js can be omitted. If it is just the module name without the path, you need to tell the engine where to look through the configuration file.

  • as keyword:
import { lastName as surname } from './profile';

The import command will be hoisted to the module header, so where it is written is not so important, but you cannot use expressions and variables to import.

  • load the whole module (no output)
import 'lodash'; //仅仅是加载而已,无法使用
  • load the whole module (with output)
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

Note:  import * ignores default output

(3) Import and export compound usage

  • Import first and then export
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, boo};
  • The whole first imports and then outputs and default
// 整体输出
export * from 'my_module';
// 导出default,正如前面所说,export default 其实导出的是default变量
export { default } from 'foo';
// 具名接口改default
export { es6 as default } from './someModule';

(4) Inheritance of modules

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

Note:  export * ignores default.

2. ES7 new features (2016)

1. Array.prototype.includes

The includes()  method is used to determine whether an array contains a specified value, and returns true if it does, otherwise returns false. This method does not change the original array. Its syntax is as follows:

arr.includes(searchElement, fromIndex)

This method has two parameters:

  • searchElement: Required, the element value to be searched.
  • fromIndex: optional, start searching for the target value from the fromIndex index. If it is a negative value, the search starts from the index of array.length + fromIndex in ascending order (even if the absolute value of fromIndex is jumped forward from the end, and then searched backward). The default is 0.
[1, 2, 3].includes(2);  //  true
[1, 2, 3].includes(4);  //  false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

Before ES7, indexOf was usually used to determine whether an array contained a specified value. However, indexOf is not clear and intuitive in semantics, and indexOf uses === to judge internally, so there is a misjudgment of NaN, and includes fixes this problem:

[1, 2, NaN].indexOf(NaN);   // -1
[1, 2, NaN].includes(NaN);  //  true

Note: Comparing strings and characters using includes() is case-sensitive.

2. Exponential operator

ES7 also introduces the exponent operator, which is used to perform exponent calculations more conveniently. It is equivalent to Math.pow():

Math.pow(2, 10));  // 1024
2**10;           // 1024

3. ES8 new features (2017)

ES8 introduces async/await, a solution to asynchronous functions. I won’t introduce it here. Please refer to the article: "Long Text, Re-learning JavaScript Asynchronous Programming"

1. pathStart() and pathEnd()

The padStart() and padEnd() methods are used to pad the length of the string. If a string is not long enough, it will be completed at the head or tail.

(1)padStart()

padStart()Used for header completion. This method has two parameters, the first parameter is a number, indicating the length of the string after completion; the second parameter is the string used for completion.

If the length of the original string is equal to or greater than the specified minimum length, the original string is returned:

'x'.padStart(1, 'ab') // 'x'

If the sum of the lengths of the string used for completion and the original string exceeds the specified minimum length, the completed string exceeding the number of digits will be truncated:

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

If the second parameter is omitted, the length is filled with spaces by default:

'x'.padStart(4, 'ab') // 'a   '

The common use of padStart() is to specify the number of digits for value completion. One of the author's recent needs is to fill the returned page number with three digits. For example, the first page is displayed as 001. You can use this method to operate:

"1".padStart(3, '0')   // 输出结果: '001'
"15".padStart(3, '0')  // 输出结果: '015'

(2)atEnd()

padEnd()Used for tail completion. This method also receives two parameters, the first parameter is the maximum length of string completion, and the second parameter is the string used for completion:

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

2. Object.values()和Object.entries()

The Object.keys method was introduced in ES5, and the Object.values ​​and Object.entries matching Object.keys were introduced in ES8 as a supplementary means of traversing an object for use in the for...of cycle. They are all used to traverse the object, and it will return an array composed of the given object's own enumerable properties (excluding inherited and Symbol properties). The order of array elements is the same as that returned when the normal loop traverses the object. , the values ​​returned by these three elements are as follows:

  • Object.keys(): Returns an array containing object keys;
  • Object.values(): returns an array containing the key values ​​of the object;
  • Object.entries(): Returns an array containing object keys and values.
let obj = { 
  id: 1, 
  name: 'hello', 
  age: 18 
};
console.log(Object.keys(obj));   // 输出结果: ['id', 'name', 'age']
console.log(Object.values(obj)); // 输出结果: [1, 'hello', 18]
console.log(Object.entries(obj));   // 输出结果: [['id', 1], ['name', 'hello'], ['age', 18]

Notice

  • The values ​​in the array returned by the Object.keys() method are all strings, that is to say, key values ​​that are not strings will be converted to strings.
  • The property values ​​in the result array are all enumerable properties of the object itself , excluding inherited properties.

3. Function extension

ES2017 stipulates that the end of the parameter list of the function can be a comma:

function person( name, age, sex, ) {}

The main function of this feature is to facilitate the modification of the same function to reduce unnecessary line changes when using git for multi-person collaborative development.

4. ES9 New Features (2018)

1. for await…of

for await...ofThe method is called an asynchronous iterator , and this method is mainly used to traverse asynchronous objects.

for await...of The statement will create an iteration cycle on asynchronous or synchronous iterable objects, including String, Array, class array, Map, Set and custom asynchronous or synchronous iterable objects. This statement can only be used async functioninside :

function Gen (time) {
  return new Promise((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()

Output result: 

image.png

2. Promise.prototype.finally()

ES2018 adds the finally() method to Promise, which means that the method will be executed regardless of whether the Promise instance finally succeeds or fails:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const one = '1';
    reject(one);
  }, 1000);
});

promise
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

The finally() function does not accept parameters, and the execution result of the promise instance is usually not known inside finally(), so operations that are not related to the state of the promise are usually performed in the finally() method.

3. Object spread operator

The spread operator was introduced in ES6, but it can only work on arrays. The spread operator in ES2018 can work on objects:​​

(1) Organize elements into objects

const obj = {a: 1, b: 2, c: 3};
const {a, ...rest} = obj;
console.log(rest);    // 输出 {b: 2, c: 3}

(function({a, ...obj}) {
  console.log(obj);    // 输出 {b: 2, c: 3}
}({a: 1, b: 2, c: 3}));

(2) Expand the object into an element

const obj = {a: 1, b: 2, c: 3};
const newObj ={...obj, d: 4};
console.log(newObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

(3) Can be used to merge objects

const obj1 = {a: 1, b:2};
const obj2 = {c: 3, d:4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

5. ES10 New Features (2019)

1. trimStart() and trimEnd()

Before ES10, JavaScript provided the trim() method to remove leading and trailing whitespace from strings. In ES9, the trimStart() and trimEnd() methods are proposed to remove the leading and trailing blanks at the beginning and end of the string. The blanks include: spaces, tabs, newlines and other blanks.

(1)trimStart()

The trimStart() method behaves the trim()same as , but returns a new string with whitespace removed from the beginning of the original string , without modifying the original string:

const s = '  abc  ';

s.trimStart()   // "abc  "

(2) trimEnd()

The trimEnd() method behaves the trim()same as , but returns a new string with whitespace removed from the end of the original string , without modifying the original string:

const s = '  abc  ';

s.trimEnd()   // "  abc"

Note that these two methods are not applicable to null, undefined, and Number types.

2. flat() and flatMap()

(1)flat()

In ES2019, the flat() method is used to create and return a new array containing the same elements as the array it calls flat() on, except that any elements that are themselves arrays will be flattened into the returned array. In the array:

[1, [2, 3]].flat()        // [1, 2, 3]
[1, [2, [3, 4]]].flat()   // [1, 2, [3, 4]]

When no parameters are passed, flat() will only flatten one level of nesting by default. If you want to flatten more levels, you need to pass a numerical parameter to flat(), which indicates the number of levels to be flattened:

[1, [2, [3, 4]]].flat(2)  // [1, 2, 3, 4]

If there are empty items in the array, they will be skipped directly:

[1, [2, , 3]].flat());    //  [1, 2, 3]

If the parameter passed in is less than or equal to 0, the original array will be returned:

[1, [2, [3, [4, 5]]]].flat(0);    //  [1, [2, [3, [4, 5]]]]
[1, [2, [3, [4, 5]]]].flat(-10);  //  [1, [2, [3, [4, 5]]]]

(2)flatMap()

The flatMap() method maps each element using the map function, then compresses the result into a new array. It's almost the same as map and flat with a depth of 1, but flatMap is usually slightly more efficient when combined into one method. This method returns a new array, each element of which is the result of the callback function, and the depth value of the structure is 1.

[1, 2, 3, 4].flatMap(x => x * 2);      //  [2, 4, 6, 8]
[1, 2, 3, 4].flatMap(x => [x * 2]);    //  [2, 4, 6, 8]

[1, 2, 3, 4].flatMap(x => [[x * 2]]);  //  [[2], [4], [6], [8]]
[1, 2, 3, 4].map(x => [x * 2]);        //  [[2], [4], [6], [8]]

3. Object.fromEntries()

The Object.fromEntries() method converts a list of key-value pairs into an object. This method is equivalent to the inverse of the Object.entries() method. The Object.entries() method returns an array of key-value pairs for a given object's own enumerable properties, while the Object.fromEntries() method converts the list of key-value pairs into an object.

const object = { key1: 'value1', key2: 'value2' }
 
const array = Object.entries(object)  // [ ["key1", "value1"], ["key2", "value2"] ]
 
 
Object.fromEntries(array)             // { key1: 'value1', key2: 'value2' }

There are two main purposes of using this method:

(1) Convert the array into an object

const entries = [
  ['foo', 'bar'],
  ['baz', 42]
]
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }

(2) Convert the Map into an object

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
])
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }

4. Symbol Description

When creating a symbol via Symbol(), a string can be provided as a description via an argument:

let dog = Symbol("dog");  // dog 为描述 

Before ES2019, getting the description of a Symbol value needs to use the String method or the toString method:

String(dog);              // "Symbol(dog)" 
dog.toString();           // "Symbol(dog)" 

ES2019 added the attribute description to directly access the description :

dog.description;  // dog

5. toString()

ES2019 extends the function's toString() method. Previously, this method would only output the function code, but omit comments and spaces. ES2019's toString() will retain comments, spaces, etc., that is, the output is the original code:

function sayHi() {
  /* dog */
  console.log('wangwang');
}

sayHi.toString();  // 将输出和上面一样的原始代码

6. catch

Before ES2019, catch will have parameters, but many times the catch block is redundant. And now without parameters:

// ES2019 之前
try {
   ...
} catch(error) {
   ...
}

// ES2019 之后
try {
   ...
} catch {
   ...
}

6. ES11 New Features (2020)

1. BigInt

In JavaScript, the numeric type Number is a 64-bit floating-point number**, so the calculation precision and representation range are limited. ES2020 has added the BigInt data type, which is also the eighth basic type introduced by JavaScript. BigInt can represent arbitrarily large integers. Its syntax is as follows:

BigInt(value);

where value is the value with which the object was created. Can be a string or an integer.

In JavaScript, the largest integer that can be represented exactly by the Number primitive type is 253. So early on there are questions like this:

let max = Number.MAX_SAFE_INTEGER;    // 最大安全整数

let max1 = max + 1
let max2 = max + 2

max1 === max2   // true

With BigInt, this problem no longer exists:

let max = BigInt(Number.MAX_SAFE_INTEGER);

let max1 = max + 1n
let max2 = max + 2n

max1 === max2   // false

You can use the typeof operator to determine whether a variable is of type BigInt (returns the string "bigint"):

typeof 1n === 'bigint'; // true 
typeof BigInt('1') === 'bigint'; // true 

You can also use Object.prototype.toStringthe method to determine whether the variable is of type BigInt (returns the string "[object BigInt]"):

Object.prototype.toString.call(10n) === '[object BigInt]';    // true

Note that BigInt and Number are not strictly equal, but loosely equal:

10n === 10 // false 
10n == 10  // true 

Number and BigInt can be compared:

1n < 2;    // true 
2n > 1;    // true 
2 > 2;     // false 
2n > 2;    // false 
2n >= 2;   // true

2. Null coalescing operator (??)

When writing code, if a property is not null and undefined, then get the property, and if the property is null or undefined, take a default value:

const name = dogName ? dogName : 'default'; 

Can be simplified with ||:

const name =  dogName || 'default'; 

However, there are certain flaws in the way of writing ||, when dogName is 0 or false, it will also go to the logic of default. So ES2020 introduces the ?? operator. Only when the left side of ?? is null or undefined, the value on the right is returned:

const dogName = false; 
const name =  dogName ?? 'default';  // name = false;

3. Optional chaining operator (?.)

During development, we often need to get deep properties, such as system.user.addr.province.name. But before getting the name attribute, you need to judge whether the previous attribute exists step by step, otherwise an error will be reported:

const name = (system && system.user && system.user.addr && system.user.addr.province && system.user.addr.province.name) || 'default';

In order to simplify the above process, ES2020 introduces the "chain judgment operator" ?., the optional chain operator ( ?. ) allows to read the value of a property located deep in the connection object chain, without having to explicitly verify each reference in the chain is it effective. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if the reference is null or undefined, the expression short-circuits and returns undefined. When used with a function call, returns undefined if the given function does not exist.

const name = system?.user?.addr?.province?.name || 'default';

The optional chaining operator will make expressions shorter and more concise when trying to access object properties that may not exist. The optional chaining operator is also helpful when exploring the contents of an object if you are not sure which properties must be present.

Optional chaining has the following three forms:

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

This operator can solve many problems when developing in TypeScript.

7. ES12 New Features (2021)

1. String.prototype.replaceAll()

The replaceAll() method will return a brand new string, all characters matching the matching rules will be replaced, and the replacement rules can be strings or regular expressions.

let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi')    // hi world, hi ES12
string.replaceAll('hello','hi')  // hi world, hi ES12

Note that when replaceAll uses regular expressions, if it is not a global match (/g), an exception will be thrown:

let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi') 
// Uncaught TypeError: String.prototype.replaceAll called with a non-global

2. Number separator

The number separator can create a visual separator between numbers, split the number by _ underscore, make the number more readable, and can be placed anywhere within the number:

const money = 1_000_000_000
//等价于
const money = 1000000000

This new feature also supports use in octal numbers:

const number = 0o123_456
//等价于
const number = 0o123456

3. Promise.any

Promise.any is a new feature of ES2021. It receives a Promise iterable object (such as an array), and as long as one of the promises succeeds, it returns the successful promise. If none of the iterable objects succeeds (that is, all promises fail/reject), returns a failed promise and an instance of type AggregateError, which is a subclass of Error and is used to aggregate a single error together

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.resolve('result'),
]

Promise.any(promises).then((value) => {
  console.log('value: ', value)
}).catch((err) => {
  console.log('err: ', err)
})

// 输出结果:value:  result

If all incoming promises fail:

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.reject('ERROR C'),
]

Promise.any(promises).then((value) => {
  console.log('value:', value)
}).catch((err) => {
  console.log('err:', err)
  console.log(err.message)
  console.log(err.name)
  console.log(err.errors)
})

Output result:

err:AggregateError: All promises were rejected
All promises were rejected
AggregateError
["ERROR A", "ERROR B", "ERROR C"]

Guess you like

Origin blog.csdn.net/Jensen_Yao/article/details/120550303