# 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])
判断数组中是否存在 searchElement,可选参数 fromIndex 表示从哪里开始搜索,如果 fromIndex 是负数,则使用 array.length + fromIndex,includes 使用 sameValueZero (opens new window) 查询 searchElement 是否存在。
# Demo
const pets = ['cat', 'dog', 'bat'];
pets.includes('cat'); // true
pets.includes('at'); // false
2
3
4
# Exponentiation (**)
# 语法
x ** y
幂操作运算符,等于 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
2
3
4
5
6
7
8
9
# ES2017 (ES8)
ES2017 新增 6 组新特性
Object.values()(opens new window) 和Object.entries()(opens new window)String.prototype.padStart()(opens new window) 和String.prototype.padEnd()(opens new window)Trailing commas in function parameter lists and calls (opens new window)
# Async Function
# 语法
async function name([param[, param[, ...param]]]) {
statements
}
2
3
异步函数,这个我们平时已经用得很多了,搭配 await 操作符 (opens new window) 一起使用。这个函数的返回一个 promise。有三点需要注意:
- 如果异步函数抛出异常,函数返回
Promise.reject()。 - 如果
await后面的promise是Promise.reject(),将导致异步函数抛出异常。 - 如果
await后面的函数抛出异常,相当于await一个reject的promise。
# 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
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)
返回对象 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]
2
3
4
5
6
7
# Object.entries()
# 语法
Object.entries(obj)
返回对象 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]]
2
3
4
5
6
7
# String.prototype.padStart()
# 语法
str.padStart(targetLength[, padString])
填充字符串:用 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"
2
3
4
5
# String.prototype.padEnd()
和 String.prototype.padStart() 一样,只是从后面填充字符串
# Object.getOwnPropertyDescriptors()
# 语法
Object.getOwnPropertyDescriptors(obj)
返回对象 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
}
}
*/
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) {}
2
3
函数调用
f(p,);
// 等同于
f(p);
2
3
# Shared Memory and Atomics
为了增强并发处理,引入了 SharedArrayBuffer 和 Atomics。SharedArrayBuffer 允许多个 Worker 共享字节,Atomics 为原子操作提供了很多静态方法。这里 (opens new window) 是这个提案的详情。
# ES2018 (ES9)
ES2018 新增 5 组新特性
Promise.prototype.finally(opens new window)- Rest/Spread Properties (opens new window)
- Asynchronous Iteration (opens new window)
- New features related to regular expression (opens new window)
- Template literals revision (opens new window)
# Promise.prototype.finally()
# 语法
p.finally(function() {
// clean
});
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
*/
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]
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}
2
3
4
# Rest parameters
function func({a, b, ...rest}) {};
# Spread Properties
展开操作符(...)将操作数自己(own)所有的可枚举属性插入到目标对象中
# 数组
const arr = [30, 40]
const arr1 = [10, 20, ...arr]
console.log(arr1); // [10, 20, 30, 40]
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}
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)
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
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
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();
}
}
2
3
4
5
6
7
8
9
10
11
# New features related to regular expression
ES2018 引入了 4 个正则表达式的新特性
# s flag
我们知道在正则表达式里,. 表示除换行符以外的任意字符。ES2018 在 RegExp 中加入 /s flag,表示. 也能匹配换行符。
console.log(/^.$/.test('\n')) // false
console.log(/^.$/s.test('\n')) // true
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
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');
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
2
3
当然也能通过 \index 反向引用(index 是捕获组的序号)
let triplicate = /^(?<part>.*).\k<part>.\1$/u;
triplicate.test('a*a*a'); // true
triplicate.test('a*a*b'); // false
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'
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'
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
2
3
相反的操作是负向零宽度先行断言 (?!pattern),匹配其后面不是 pattern 的字符串,例如匹配三位数字,而且这三位数字的后面不能是数字
const regExp = /\d{3}(?!\d)/;
console.log(regExp.exec('1234')[0]); // 234
2
# Lookbehind Assertions
零宽度后行断言 (?<=pattern),用于匹配 pattern 后面的字符串,这个 pattern 不在匹配内,只是作为一个前置标志,例如下面是匹配美元符号($)后面的金额数字:
const regExp = /(?<=\$)\d+(\.\d*)?/;
console.log(regExp.exec('$199')[0]); // 199
console.log(regExp.exec('199')); // null
2
3
相反的操作是负向零宽度后行断言 (?<!pattern),匹配其前面没有 pattern 的字符串,例如下面匹配前面不是美元符号($)的金额数字:
const regExp = /(?<!\$)\d+(\.\d*)?/;
console.log(regExp.exec('¥199')[0]); // 199
console.log(regExp.exec('$199')[0]); // 99 (1 不是 $)
2
3
# Unicode Property Escapes
使用 \u 标识符和 \p{…},匹配 Unicode 字符,例如匹配希腊字符
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
2
匹配空白字符
const result = /\p{White_Space}+/u.test('3 empanadas');
console.log(result) // Prints true
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这种则是错误的\OOO8进制转义 (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" }
2
3
4
5
6
7
但是在 Template literals 和普通字符串(用双引号或者单引号包起来的)中依然是错误的,会抛出 SyntaxError 异常,如
let bad = `bad escape sequence: \unicode`;
# ES2019 (ES10)
ES2019 新增 8 组新特性
Array.prototype.flat()(opens new window) 和Array.prototype.flatMap()(opens new window)String.prototype.trimStart()(opens new window) 和String.prototype.trimEnd()(opens new window)Object.fromEntries()(opens new window)Symbol.prototype.description(opens new window)- Optional
catchbinding (opens new window) - JSON superset (opens new window)
- Well-formed
JSON.stringify(opens new window) Function.prototype.toStringrevision (opens new window)
# Array.prototype.flat()
# 语法
arr.flat(depth = 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]
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]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Array.prototype.flatMap()
# 语法
arr.flatMap((currentValue, index, array) => { /* ... */ } )
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
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
2
3
4
5
6
# Object.fromEntries()
# 语法
Object.fromEntries(iterable);
将 [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 }
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"
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).
# Optional catch binding
ES2019 之前 try...catch (opens new window) 中的 catch 块必须绑定一个错误变量,ES2019 提议这个错误变量是可选的了
// 以前
try {
...
} catch(error) {
...
}
// 现在
try {
...
} catch {
...
}
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",以前是 "�"
# Function.prototype.toString revision
Function.prototype.toString 现在返回的字符串中包含函数的空白字符和注释
function /* a comment */ foo () {}
console.log(foo.toString());
// 以前输出
"function foo() {}"
// 现在输出
"function /* comment */ foo () {}"
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
2
JavaScript 终于也引入了这个操作符。
const a = null // 或者 undefined
const b = a?.count
2
当 a 是 null 或者 undefined,直接返回 undefined 给 b,不会像 . 操作符报错,说
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
2
3
4
5
6
7
8
9
# 语法
obj.val?.prop
obj.val?.[expr]
obj.arr?.[index]
obj.func?.(args)
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
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
等价于
const res = (leftExpr !== null && leftExpr !== undefined) ? leftExpr : rightExpr
当 leftExpr 是 null 或 undefined 是,res 为 rightExpr, 否则 res 为 leftExpr
主要区别于逻辑或 ||
const res = leftExpr || rightExpr
逻辑或 || 是当 leftExpr 是 falsy (opens new window) 时,res 为 rightExpr, 否则 res 为 leftExpr
?? 和 逻辑或 || 一样,当左边表达式的值不是 null 或 undefined,右边的表达式不会运算
# 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
2
3
4
5
6
7
8
9
10
11
# String.prototype.matchAll()
# 语法
str.matchAll(regexp)
参数 regexp,是一个 RegExp 对象 ,如果不是,将使用 new RegExp(regexp) 转换成 RegExp 对象。这个 RegExp 对象必须带有 \g flag,否则抛出 TypeError 错误
它返回一个迭代器,其中每个元素是匹配成功的数组,带有额外的 index 和 input 属性。数组的第一元素是匹配的字符串,后面是对应的捕获组。
迭代器元素跟带 \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."
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."
2
3
4
5
6
7
8
9
10
matchAll()在函数内部复制了regexp, 所以不像RegExp.prototype.exec(),regexp的lastIndex没有改变
# Promise.allSettled()
# 语法
Promise.allSettled(iterable);
等待所有的 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' }
*/
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
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")
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')
}
2
3
# for-in mechanics
ES2020 规定 for (x in y) 应以何种顺序访问 y 的属性,这样每次迭代的顺序是固定的。外部未知,属于内部实现细节。
# Dynamic Import import()
以前我们只能在文件的顶部静态地导入模块,如
import _ from "lodash"
静态导入导致一开始就加载了模块的所有代码,增加了加载负荷,但是可能这些模块暂时是不需要的,这个时候就需要动态导入。
ES2020 引入动态导入 import(),可以按需导入模块。
import() 函数返回 promise
let module = await import('/modules/my-module.js');
# 路由懒加载
路由懒加载 (opens new window) 就是使用了动态导入,当路由被访问的时候才加载对应组件。
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
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"
2
3
4
5
6
7
# Source Code
cp3hnu/What-s-New-in-ECMAScript (opens new window)
# 应用地址
# References
- tc39/finished-proposals (opens new window)
- MDN (opens new window)
- Useful New Features in ES2016 and 2017 (opens new window)
- What’s new in ES2018 (ES9) (opens new window)
- What’s new in JavaScript ES2019 (opens new window)
- What’s new in JavaScript — ES2020 (opens new window)
- CanIUse (opens new window)
- Compat-Table (opens new window)
- node.green (opens new window)
- core-js (opens new window)
- Polyfill.io (opens new window)
- es-shims (opens new window)