# ES2016 ~ ES2020 新特性

我们知道 ECMAScript 在 ES2015(即 ES6)引入很多很多的新特性,比如箭头函数、class、promise、proxy、reflect 等等,详情可以参考 Babel 的 Learn ES2015 (opens new window)。在 ES2015 以后 ECMAScript 每年也陆续引入一些新特性,这篇文章总结了从 ES2016 到 ES2020 引入的新特性。

这里 (opens new window) 是 ECMAScript 2016 ~ 2020 所有通过的提案

这里 (opens new window) 是我做的一个 web 应用,可以运行代码,查看结果

使用这些新特性之前,建议用 CanIUse (opens new window),查一下浏览器的兼容性

如果浏览器不支持可以自行 polyfill:core-js (opens new window), Polyfill.io (opens new window)

# ES2016 (ES7)

ES2016 新增 2 个新特性

# Array.prototype.includes()

# 语法

arr.includes(searchElement[, fromIndex])
1

判断数组中是否存在 searchElement,可选参数 fromIndex 表示从哪里开始搜索,如果 fromIndex 是负数,则使用 array.length + fromIndexincludes 使用 sameValueZero (opens new window) 查询 searchElement 是否存在。

# Demo

const pets = ['cat', 'dog', 'bat'];

pets.includes('cat'); // true
pets.includes('at'); // false
1
2
3
4

# Exponentiation (**)

# 语法

x ** y
1

幂操作运算符,等于 Math.pow,除了它可以接受 BigInt 类型的数据,有以下两点注意事项

  • 它是右结合运算符,即 a ** b ** c 等于 a ** (b ** c)
  • 不能把一元运算符(+/-/~/!/delete/void/typeof)放在基数的前面,所以 -2 ** 2 会抛出异常,应该使用 - (2 ** 2) 或者 (-2) ** 2

# Demo

2 ** 2 // 4
2 ** 10 // 1024
// 右结合
2 ** 2 ** 3 // 2 ** (2 ** 3),即2的8次方256
-(2 ** 2) // -4
(-2) ** 2 // 4

// 下面这个会报异常
// -2 ** 2
1
2
3
4
5
6
7
8
9

# ES2017 (ES8)

ES2017 新增 6 组新特性

# Async Function

# 语法

async function name([param[, param[, ...param]]]) {
   statements
}
1
2
3

异步函数,这个我们平时已经用得很多了,搭配 await 操作符 (opens new window) 一起使用。这个函数的返回一个 promise。有三点需要注意:

  • 如果异步函数抛出异常,函数返回 Promise.reject()
  • 如果 await 后面的 promisePromise.reject(),将导致异步函数抛出异常。
  • 如果 await 后面的函数抛出异常,相当于 await 一个 rejectpromise

# Demo

function defer() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(1024)
    }, 2000)
  })
}

async function asyncFunc() {
  const result = await defer()
  console.log('result =', result)
}

asyncFunc() // result = 1024
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意要使用 try...catch (opens new window) 处理 await 失败的情况,或者使用 await func().catch(e){...}

# Object.values()

# 语法

Object.values(obj)
1

返回对象 obj 自己的(own)可枚举的且不是 Symbol 的属性值数组

Object.keys(obj) (opens new window) 返回对象 obj 自己的(own)可枚举的属性名数组

# Demo

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

Object.values(object1)  // ['somestring', 42, false]
1
2
3
4
5
6
7

# Object.entries()

# 语法

Object.entries(obj)
1

返回对象 obj 自己的(own)可枚举的且不是 Symbol 的属性名和属性值组成的数组的数组,即 [[key1, value1], [key2, value2]]

# Demo

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

Object.entries(object1)  // [['a', 'somestring'], ['b', 42], ['c', false]]
1
2
3
4
5
6
7

# String.prototype.padStart()

# 语法

str.padStart(targetLength[, padString])
1

填充字符串:用 padString (默认是空格)从字符串的开始位置填充字符串,使字符串的总长度达到 targetLength

如果 padString.length + str.length > targetLength 则使用 padString 的一部分字符填充。

如果 padString.length + str.length < targetLength 则使用多个 padString 填充。

# Demo

'abc'.padStart(10);         // "       abc"
'abc'.padStart(8, "0");     // "00000abc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(1);          // "abc"
'abc'.padStart(10, "foo");  // "foofoofabc"
1
2
3
4
5

# String.prototype.padEnd()

String.prototype.padStart() 一样,只是从后面填充字符串

# Object.getOwnPropertyDescriptors()

# 语法

Object.getOwnPropertyDescriptors(obj)
1

返回对象 obj 自己的(own)属性描述,包括不可枚举属性和 Symbol 属性

# Demo

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

Object.getOwnPropertyDescriptors(object1)
/*
{
  a: {
    value:"somestring",
    writable:true,
    enumerable:true,
    configurable:true
  },
  b: {
    value:42,
    writable:true,
    enumerable:true,
    configurable:true
  },
  c: {
    value:false,
    writable:true,
    enumerable:true,
    configurable:true
  }
}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Trailing commas in function parameter lists and calls

在定义函数参数和调用函数的时候,末尾添加逗号是合法的,例如

定义函数参数

function f(p,) {} 
// 等同于
function f(p) {}
1
2
3

函数调用

f(p,);
// 等同于
f(p);
1
2
3

# Shared Memory and Atomics

为了增强并发处理,引入了 SharedArrayBufferAtomicsSharedArrayBuffer 允许多个 Worker 共享字节,Atomics 为原子操作提供了很多静态方法。这里 (opens new window) 是这个提案的详情。

# ES2018 (ES9)

ES2018 新增 5 组新特性

# Promise.prototype.finally()

# 语法

p.finally(function() {
   // clean
});
1
2
3

无论 Promise 是 resolve 还是 reject,finally() 都会执行,一般用于最后的执行清理工作,比如关闭文件等

finally()then()catch() 函数一样,都是返回 Promise,这样可以级联使用

# Demo

function checkMail() {
  return new Promise((resolve, reject) => {
    if (Math.random() > 0.5) {
      resolve('Mail has arrived');
    } else {
      reject(new Error('Failed to arrive'));
    }
  });
}

checkMail()
  .then((mail) => {
    console.log(mail);
  })
  .catch((err) => {
    console.error(err);
  })
  .finally(() => {
    console.log('Experiment completed');
  });

/*
	output:
	Mail has arrived
	Experiment completed
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Rest/Spread Properties

ES2015 引入了 解构赋值 (opens new window)展开语法 (opens new window),也引入了应用于数组的 Rest/Spread Elements

ES2018 引入了应用于对象的 Rest/Spread Properties

# Rest Properties

在对象解构赋值中,rest 操作符(…)将原对象自己(own)所有的可枚举属性复制到 rest properties 中(除了那些已经在对象字面量中提到的属性)

# 数组
const {a, b, ...rest} = [10, 20, 30, 40]
console.log(a);     // 10
console.log(a);     // 20
console.log(rest);  // [30, 40]
1
2
3
4
# 对象
const {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
console.log(a);     // 10
console.log(a);     // 20
console.log(rest);  // {c: 30, d: 40}
1
2
3
4
# Rest parameters
function func({a, b, ...rest}) {};
1

# Spread Properties

展开操作符(...)将操作数自己(own)所有的可枚举属性插入到目标对象中

# 数组
const arr = [30, 40]
const arr1 = [10, 20, ...arr]
console.log(arr1); // [10, 20, 30, 40]
1
2
3
# 对象
const obj = {
  c: 30,
  d: 40
}
const obj1 = {
  a: 10, 
  b: 20,
  ...obj
}
console.log(obj1) // {a: 10, b: 20, c: 30, d: 40}
1
2
3
4
5
6
7
8
9
10
# Spread 操作符 VS Object.assign

Spread 操作符实现了 Object.assign (opens new window) 的功能,但是不会改变 source target,且语法更加简单

const obj1 = {
  a: 10,
  b: 20
}const obj2 = {
  c: 30,
  d: 40
}
const obj3 = {
  ...obj1,
  ...obj2
}
const obj4 = Object.assign({}, obj1, obj2)
1
2
3
4
5
6
7
8
9
10
11
12
13

# Asynchronous Iteration

ES2015 引入了迭代器和生成器 (opens new window)。为了实现异步迭代,ES2018 引入了异步迭代器(Async Iterator)异步生成器(Async Generator)异步迭代语句( for await of) (opens new window)

# Async Iterator

AsyncIterator 和 Iterator 一样,有一个 next 方法,不同的是 AsyncIterator 的 next 方法返回的是 Promise.resolve({ value, done }) 或者 Promise.reject(error),这种情况 for await of 将抛出异常。

AsyncIterator 的 next 方法也可以直接返回 { value, done } 对象

# Symbol.asyncIterator

和同步迭代一样,支持异步迭代的对象必须实现 Symbol.asyncIterator 方法,返回一个异步迭代器 AsyncIterator

# Async Generator

通过 async *function 实现异步生成器,异步生成器和同步生成器结构是一样的。异步生成器搭配 yield wait 语句, next 方法返回的是 Promise.resolve({ value, done }) 或者 Promise.reject(error),这种情况 for await of 将抛出异常。

# for await of

迭代异步可迭代对象使用 for await of 语句。

for await of 语句只能用于异步函数中(async function)

# Demo

下面通过一个例子来了解异步迭代

class AsynchronousIteration {
  constructor(max) {
    this.max = max
  }
  [Symbol.asyncIterator]() {
    const max = this.max
    let count = 0
    return {
      next() {
        if (count <= max) {
          return new Promise((resolve) => {
            resolve({value: count++, done: false})
          }, 1000)
        } else {
          return new Promise((resolve) => {
            resolve({value: count++, done: true})
          }, 1000)
        }
      }
    }
  }
}

async function testAsyncIteration() {
  const iterable = new AsynchronousIteration(5)
  for await (const count of iterable) {
    console.log(count)
  }
}

testAsyncIteration() // 1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

for await of 也可以用于同步迭代器,比如数组,但是有一点需要注意,就是如果同步迭代返回的元素是 Promise,则 for await of 会等待 Promise fulfilled 或者 rejected

const arr = [1, 2, Promise.resolve(3)]
function testSync() {
  for (const i of arr) {
    console.log(i)
  }
}
async function testAsync() {
  for await (const i of arr) {
    console.log(i)
  }
}

testSync()  // 1, 2, Promise { 3 }
testAsync() // 1, 2, 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14

异步生成器

async function* readLines(path) {
  let file = await fileOpen(path);

  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}
1
2
3
4
5
6
7
8
9
10
11

ES2018 引入了 4 个正则表达式的新特性

# s flag

我们知道在正则表达式里,. 表示除换行符以外的任意字符。ES2018 在 RegExp 中加入 /s flag,表示. 也能匹配换行符。

console.log(/^.$/.test('\n')) // false
console.log(/^.$/s.test('\n')) // true
1
2

同时 RegExp 添加了 RegExp.prototype.dotAll属性,表示 RegExp 中是否包含 /s flag

# Named capture groups

以前 RegExp 执行 exec ,只能通过 index 获取对应的捕获组,比如

const eventDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const matchedObject = eventDate.exec('2022-04-09');
console.log(matchedObject[1]); // 2022
console.log(matchedObject[2]); // 04
console.log(matchedObject[3]); // 09
1
2
3
4
5

ES2018 通过 (?<name>...) 可以给捕获组命名

const eventDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
const matchedObject = eventDate.exec('2022-04-03');
console.log(matchedObject.groups.year);  // 2022
console.log(matchedObject.groups.month); // 04
console.log(matchedObject.groups.day);   // 09

// 还可以使用对象解构赋值
const { groups: {year, day, month} } = eventDate.exec('2022-04-09');
1
2
3
4
5
6
7
8

命名捕获组还能通过 \k<name> 反向引用,例如

let duplicate = /^(?<half>.*).\k<half>$/u;
duplicate.test('a*b'); // false
duplicate.test('a*a'); // true
1
2
3

当然也能通过 \index 反向引用(index 是捕获组的序号)

let triplicate = /^(?<part>.*).\k<part>.\1$/u;
triplicate.test('a*a*a'); // true
triplicate.test('a*a*b'); // false
1
2
3

用于 String.prototype.replace 方法时,在替换字符串中可以通过 $<name> 表示正则表达式中的捕获组,例如

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = '2015-01-02'.replace(re, '$<day>/$<month>/$<year>');
// result === '02/01/2015'
1
2
3

如果 String.prototype.replace 的第二个参数是函数,在这个函数的最后新增了一个 groups 参数,表示所有的捕获组

function (matched, capture1, ..., captureN, offset, S, groups)

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = '2015-01-02'.replace(re, (...args) => {
 let {day, month, year} = args[args.length - 1];
 return `${day}/${month}/${year}`;
});
// result === '02/01/2015'
1
2
3
4
5
6

# Lookahead Assertions

零宽度先行断言(?=pattern) ,用于匹配 pattern 前面的字符串 ,这个 pattern 不在匹配内,只是作为一个后置标志,例如下面匹配结尾是 ing 的字符串

const regExp = /\w+(?=ing)/;
console.log(regExp.exec('starting')[0]); // start
console.log(regExp.exec('start')); // null
1
2
3

相反的操作是负向零宽度先行断言 (?!pattern),匹配其后面不是 pattern 的字符串,例如匹配三位数字,而且这三位数字的后面不能是数字

const regExp = /\d{3}(?!\d)/;
console.log(regExp.exec('1234')[0]); // 234
1
2

# Lookbehind Assertions

零宽度后行断言 (?<=pattern),用于匹配 pattern 后面的字符串,这个 pattern 不在匹配内,只是作为一个前置标志,例如下面是匹配美元符号($)后面的金额数字:

const regExp = /(?<=\$)\d+(\.\d*)?/;
console.log(regExp.exec('$199')[0]); // 199
console.log(regExp.exec('199')); // null
1
2
3

相反的操作是负向零宽度后行断言 (?<!pattern),匹配其前面没有 pattern 的字符串,例如下面匹配前面不是美元符号($)的金额数字:

const regExp = /(?<!\$)\d+(\.\d*)?/;
console.log(regExp.exec('¥199')[0]); // 199
console.log(regExp.exec('$199')[0]); // 99 (1 不是 $)
1
2
3

# Unicode Property Escapes

使用 \u 标识符和 \p{…},匹配 Unicode 字符,例如匹配希腊字符

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
1
2

匹配空白字符

const result = /\p{White_Space}+/u.test('3 empanadas');
console.log(result) // Prints true
1
2

\P{...}\p{…}意思相反,表示不匹配。

这个平时我很少用到,这里 (opens new window) 有详细的描述和例子。

# Template literals revision

ES2015 (ES 6) 引入了 Template literals (opens new window)Tagged templates (opens new window),但是如果在模板字符串中存在下列情形的,则抛出 SyntaxError 异常

  • \uXXXX:unicode 转义,如 \u00A9 表示 ©,但是像 \ubuntu 这种则是错误的
  • \u{Code Point}:和 \uXXXX 一样是 unicode 转义,但是可以放置一个 码位(code point) (opens new window),码点是 Unicode 中一个字符的完整标识,可能是16位,也可能是32位,例如 \u{1F60A} 表示一个笑脸(😊), 但是像 \u{ubuntu} 这种则是错误的
  • \xHH:16 进制转义,如 \xA9 表示 ©,但是像 \xerxes 这种则是错误的
  • \OOO 8进制转义 (opens new window)已经 deprecated,所以像 \251 这种也是错误的

在 ES2018 的 Tagged templates,上面这些将不再抛出 SyntaxError 异常,而是返回 undefined,如

function latex(str) {
  return { "cooked": str[0], "raw": str.raw[0] }
}

latex`\unicode`

// { cooked: undefined, raw: "\\unicode" }
1
2
3
4
5
6
7

但是在 Template literals 和普通字符串(用双引号或者单引号包起来的)中依然是错误的,会抛出 SyntaxError 异常,如

let bad = `bad escape sequence: \unicode`;
1

# ES2019 (ES10)

ES2019 新增 8 组新特性

# Array.prototype.flat()

# 语法

arr.flat(depth = 1)
1

flat() 就是将一个嵌套数组打平,depth 控制解套的层级,默认值是 1,数组本身不改变,返回打平后的数组

# Demo

const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat()); // [0, 1, 2, 3, 4]

const arr2 = [0, 1, 2, [[3, 4]]];
// 默认只打平一层嵌套数组
console.log(arr2.flat()); // [0, 1, 2, [3, 4]]
console.log(arr2.flat(2)); // [0, 1, 2, 3, 4]
1
2
3
4
5
6
7

这里 (opens new window) 列举了很多实现 flat 函数的方法,比如使用生成器

function* flatten(array, depth) {
  if (depth === undefined) {
    depth = 1;
  }

  for (const item of array) {
    if (Array.isArray(item) && depth > 0) {
      yield* flatten(item, depth - 1);
    } else {
      yield item;
    }
  }
}

const arr = [1, 2, [3, 4, [5, 6]]];
const flattened = [...flatten(arr, Infinity)]; // [1, 2, 3, 4, 5, 6]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Array.prototype.flatMap()

# 语法

arr.flatMap((currentValue, index, array) => { /* ... */ } )
1

flatMap() 等价于 flat(1).map()currentValue 是数组的元素,index 是数组的索引,array 是数组本身。数组本身不改变,返回新的数组

# Demo

const arr = [1, 2, 3, 4];
const arr2 = arr.flatMap(x => x * x); // 1, 4, 9, 16
1
2

# String.prototype.trimStart() 和 String.prototype.trimEnd()

移除字符串前面(trimStart)或者后面(trimEnd)的空白字符,字符串本身不改变,返回去掉空白字符的字符串

# Demo

let str = '   foo  ';
console.log(str.length); // 8  
str = str.trimStart(); 
console.log(str.length); // 5 
str = str.trimEnd();
console.log(str.length); // 3  
1
2
3
4
5
6

# Object.fromEntries()

# 语法

Object.fromEntries(iterable);
1

[key, value] 对的列表(Array,Map 等可迭代对象)转换成对象,是 Object.entries() (opens new window) 的反向操作

# Demo

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);
const obj = Object.fromEntries(entries);
console.log(obj); // { foo: "bar", baz: 42 }
1
2
3
4
5
6

# Symbol.prototype.description

创建 Symbol 的时候可以带一个可选的 description 参数,例如: Symbol("foo"),这个属性就是获取 Symbol 的 description("foo"),不同的是创建 Symbol 时 description 可以是非字符串类型,例如 Number 类型,但是 description 属性返回的是其字符串形式。

description 属性不同于 Symbol.prototype.toString() ,前者没有用 "Symbol()" 字符串包裹 description

# Demo

console.log(Symbol("desc").description);     // "desc"
console.log(Symbol("desc").toString());     // "Symbol(desc)"
// 注意这里是字符串不是数字
console.log(Symbol(22).description);        // "22"
console.log(Symbol.for('foo').description); // "foo"
1
2
3
4
5

Symbol 虽然有 toString()方法 , 但是 Symbol 不能转换成字符串,比如 ${Symbol("desc")} 将抛出 TypeError,这是因为

According to ECMA-262, using the addition operator (opens new window) on a value of type Symbol in combination with a string value first calls the internal ToPrimitive (opens new window), which returns the symbol. It then calls the internal ToString (opens new window) which, for Symbols, will throw a TypeError exception.

So calling the internal ToString is not the same as calling Symbol.prototype.toString (opens new window).

答案来源于 stackoverflow (opens new window)

# Optional catch binding

ES2019 之前 try...catch (opens new window) 中的 catch 块必须绑定一个错误变量,ES2019 提议这个错误变量是可选的了

// 以前
try {
   ...
} catch(error) {
   ...
}

// 现在
try {
   ...
} catch {
   ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# JSON superset

扩展 ECMA-262 成为 JSON 的超集。现在在字符串中允许存在行分隔符(U+2028)和段落分隔符(U+2029)。以前它们被视为行终止符,并导致 SyntaxError 异常。详见 提案 (opens new window)

# Well-formed JSON.stringify

ES2019 使用 JSON 转义序列来表示未配对代理代码点(例如 U+D800 ~ U+DFFF),而不是奇怪的字符。详见 提案 (opens new window)

console.log(JSON.stringify('\uD800')); // "\ud800",以前是 "�"
1

# Function.prototype.toString revision

Function.prototype.toString 现在返回的字符串中包含函数的空白字符和注释

function /* a comment */ foo () {}

console.log(foo.toString());

// 以前输出
"function foo() {}"


// 现在输出
"function /* comment */ foo () {}"
1
2
3
4
5
6
7
8
9
10

# ES2020 (ES11)

ES2020 新增 9 组新特性

# Optional Chaining (?.)

可选链操作符 ?.,iOS Swift 开发的同学对这个再熟悉不过了

let str: String? = nil
let count = str?.count // count is nil
1
2

JavaScript 终于也引入了这个操作符。

const a = null // 或者 undefined
const b = a?.count
1
2

anull 或者 undefined,直接返回 undefinedb,不会像 . 操作符报错,说

Cannot read property 'count' of null

这个操作符对访问深层次的属性很有帮助,不需要一层一层的判断对象是否存在了

// 以前
let property = ...
if (obj && obj.first && obj.first.array && obj.first.array[0]) {
  property = obj.first.array[0].property
}


// 现在
const property = obj?.first?.array?.[0]?.property
1
2
3
4
5
6
7
8
9

# 语法

obj.val?.prop
obj.val?.[expr]
obj.arr?.[index]
obj.func?.(args)
1
2
3
4

支持对象、数组和方法可选链式

# Demo

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  },
};

// 对象
console.log(adventurer.dog?.name); // undefined

// 方法
console.log(adventurer.someNonExistentMethod?.()); // undefined

//数组
console.log(adventurer.list?.[0]) // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Nullish coalescing Operator (??)

空值合并运算符 ??,iOS Swift 开发的同学对这个也很熟悉

难道是 JavaScript 从 Swift 语言借鉴了这两个操作符(可选链操作符 ?. 和 空值合并运算符 ??),就是 Swift 5.5 借鉴了 Javascript 的 Async Function 和 await 操作符一样。😁

# 语法

const res = leftExpr ?? rightExpr
1

等价于

const res = (leftExpr !== null && leftExpr !== undefined) ? leftExpr : rightExpr
1

leftExprnullundefined 是,resrightExpr, 否则 resleftExpr

主要区别于逻辑或 ||

const res = leftExpr || rightExpr
1

逻辑或 || 是当 leftExpr 是 falsy (opens new window) 时,resrightExpr, 否则 resleftExpr

?? 和 逻辑或 || 一样,当左边表达式的值不是 nullundefined,右边的表达式不会运算

# Demo

const myText = '';
const otherText = null;

const preservingFalsy = myText ?? 'Hi neighborhood';
console.log(preservingFalsy);   // ''

const neighborhood = otherText ?? 'Hi neighborhood';
console.log(neighborhood);     // 'Hi neighborhood'

const notFalsyText = myText || 'Hello world';
console.log(notFalsyText);     // Hello world
1
2
3
4
5
6
7
8
9
10
11

# String.prototype.matchAll()

# 语法

str.matchAll(regexp)
1

参数 regexp,是一个 RegExp 对象 ,如果不是,将使用 new RegExp(regexp) 转换成 RegExp 对象。这个 RegExp 对象必须带有 \g flag,否则抛出 TypeError 错误

它返回一个迭代器,其中每个元素是匹配成功的数组,带有额外的 indexinput 属性。数组的第一元素是匹配的字符串,后面是对应的捕获组。

迭代器元素跟带 \g flag RegExp.prototype.exec() (opens new window) 函数返回值是一样的。下面是这两个函数的 demo.

# Demo

# RegExp.prototype.exec()
const regexp = new RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
let match;

while ((match = regexp.exec(str)) !== null) {
  console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
}

// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
1
2
3
4
5
6
7
8
9
10
# String.prototype.matchAll()
const regexp = new RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);

for (const match of matches) {
  console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
}

// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
1
2
3
4
5
6
7
8
9
10

matchAll() 在函数内部复制了 regexp, 所以不像 RegExp.prototype.exec()regexplastIndex 没有改变

# Promise.allSettled()

# 语法

Promise.allSettled(iterable);
1

等待所有的 promise resovle 或者 reject。返回一个 Promise.resovle([{status, value | reason}])

status 是 'fulfilled' 或者 'rejected',另一个属性是

  • 当 promise 是 resovle 时,返回 value
  • 当 promise 是 reject 时,返回 reason

# Demo

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result)));

/* 
{ status: 'fulfilled', value: 3 }
{ status: 'rejected', reason: 'foo' }
*/
1
2
3
4
5
6
7
8
9
10
11

# BigInt

ES2020 引入了一个新的数据类型 BigInt。顾名思义用于处理大的整数的,它是一个内部 bigint 类型的封装对象,类似于与 Number(封装 number 类型)

目前位置 JavaScript 有 number、string、boolean、null、undefined、Object、Symbol 和 BigInt 8 个基础类型

console.log('\n**BigInt**')
const a = BigInt(0)
console.log(typeof a) // bigint
1
2
3

可以在数字后面附加一个 n,表示这是一个 BigInt,或者使用 BigInt 构造函数,注意前面不要加 new 关键字。下面都是创建 BigInt 的方式

// 数字字面量后面附加 n
const previouslyMaxSafeInteger = 9007199254740991n

// BigInt 构造函数
const alsoHuge = BigInt(9007199254740991)

// BigInt 构造函数的参数可以是字符串,数字和字符串可以是10进制、16进制、8进制和2进制
const hugeString = BigInt("9007199254740991")

// 16进制
const hugeHex = BigInt("0x1fffffffffffff")

// 8进制
const hugeOctal = BigInt("0o377777777777777777")

// 2进制
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 主要事项

使用 BigInt 时,要注意

  • 不能使用 Math 的方法,例如 abs(),会报错:Cannot convert xxx BigInt value to a number
  • BigInt 支持 + * - % ** 运算,但是两个操作数要统一类型,不能混用 BigInt 和 number,例如 1n + 2,会报错:Cannot mix BigInt and other types, use explicit conversions
  • 除法运算(/),只返回整数,例如 5n / 2n = 2n
  • BigInt 运算结果是 BigInt 类型
  • BigInt 不支持一元运算符 +
  • BigInt 支持位运算符 (opens new window),除了 >>>
  • BigInt 可以和 number 类型进行比较,如大于(>)、小于(>)和 等于(==)等
  • 0n 也是 falsy (opens new window)

# globalThis

以前全局对象(global)在不同的运行环境有不同的名字,浏览器是 window, Node 是 global, web workers 是 self。现在统一使用 globalThis 表示全局对象,下面的代码在浏览器里输出 browers

if (globalThis === window){
  console.log('browser')
}
1
2
3

# for-in mechanics

ES2020 规定 for (x in y) 应以何种顺序访问 y 的属性,这样每次迭代的顺序是固定的。外部未知,属于内部实现细节。

# Dynamic Import import()

以前我们只能在文件的顶部静态地导入模块,如

import _ from "lodash"
1

静态导入导致一开始就加载了模块的所有代码,增加了加载负荷,但是可能这些模块暂时是不需要的,这个时候就需要动态导入。

ES2020 引入动态导入 import(),可以按需导入模块。

import() 函数返回 promise

let module = await import('/modules/my-module.js');
1

# 路由懒加载

路由懒加载 (opens new window) 就是使用了动态导入,当路由被访问的时候才加载对应组件。

const UserDetails = () => import('./views/UserDetails')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})
1
2
3
4
5
6

# import.meta

使用 import.meta 访问 module 的元数据,比如 url

<script type="module" src="es2020.js"></script>

// es2020.js
console.log(import.meta); 
// in replit.com
// { url: 'https://5511b908-bf3c-426b-be8b-a6c25d39cb67.id.repl.co/es2020.js' }
// playcode 不支持 type="module"
1
2
3
4
5
6
7

# Source Code

cp3hnu/What-s-New-in-ECMAScript (opens new window)

# 应用地址

New-In-JS/ (opens new window)

# References