2

本文简略记录了一下ES2018到ES2021中新增的特性,可以点击新特性详细了解一下。

ES2018/ES9

非转义序列的模板字符串

移除了在带标签的模板字符串中转义序列的语法限制
之前,\u开始一个unicode转义,\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:\uuu\xxx\111

正则表达式Unicode转义

之前, 可以通过字符集的名称来匹配字符, 即s代表空白
/^\s+$/u.test(' ') // true
在es9,添加了Unicode属性转义,形式为\p{...}\P{...},在正则表达式使用标记u

/^\p{White_Space}+$/u.test(' ') // 空格
// true
/^\p{Script=Greek}+$/u.test('μετά') // 希腊字母
// true
/^\p{Script=Latin}+$/u.test('Grüße') // 匹配拉丁字母

正则表达式s标记

之前.可以匹配任意字符,除了换行符
es9后,可以通过标记s,这样.就可以匹配换行符
/hello.es9/s.test('hello\nes9') //true

RegExp named capture groups(正则表达式命名捕获组)

捕获分组和非捕获分组

()表示捕获分组,()会把每个分组里的匹配的值保存起来(存储在内存中),捕获的子序列稍后可以通过 Back 引用(反向引用) 在表达式中使用
(?)开头的就是非捕获分组,不会将匹配的值保存起来,也不能后面引用,其中?:、?=、?<=等都是非捕获元,使用这些非捕获元的分组为非捕获分组
捕获分组都是通过索引编号的, 这样代码可读性差

const reg = /(\d{4})-(\d{2})-(\d{2})/u;
const matched = reg.exec('2018-12-31');
matched[0];  // 2018-12-12
matched[1];  // 2018
matched[2];  // 12
matched[3];  // 31

命名捕获组

es9中,允许命名捕获组使用符号?<name>, 小括号中匹配内容的名称放在groups里,提高了代码的可读性

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
const matched = reg.exec('2018-12-31')
matched.groups.year;  // 2018
matched.groups.month;  // 12
matched.groups.day;  // 31

命名捕获组也可以使用在replace()方法中
例如将日期转换为“年月日”格式:

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2018-12-31'.replace(reg, '$<year>年$<month>月$<day>日');
// 2018年12月31日

正则表达式反向断言(lookbehind)

正向先行断言:?=

形式: (?=pattern)
exp1(?=exp2): 查找后面为exp2的exp1

const reg = /runoob(?=[\d+])/g //匹配数字前面的runoob
123456runoob123runoobbdhh // 只会匹配第一个runoob
23runoob456runoob789 //两个都好匹配到

负向先行断言: ?!

形式: (?!pattern)
exp1(?!exp2): 查找后面不为exp2的exp1

const reg = /runboo(?![0-9]+)/g // 匹配后面不是数字的runboo
1233runboo-google12runoob1233 // 匹配第一个

正向后行断言(es9): ?<=

形式: (?<=pattern)
(?<=exp2)exp1: 查找前面是exp2的exp1

const reg = /(?<=[0-9]+)runboo/g //匹配前面是数字的runboo
1234google123runboo456 // 匹配runboo
123googlerunboo123runboo456 // 匹配第二个

负向后行断言(es9):?<!

形式: (?<!pattern)
(?<!exp2)exp1: 查找前面不是exp2的exp1

const reg = /(?<=[0-9]+)runboo/g //匹配前面不是数字的runboo
1234google123runboo456 // 不能匹配到runboo
123googlerunboo123runboo456 // 匹配第一个

扩展运算符(...)

之前...只能用于数组,es9后,也可以用于对象了,用法和数组一样

Promise.finally()

没有参数
之前的Promise的结果要么是成功then要么是失败catch,使得有些公共逻辑代码必须在两个回调函数里面写(例如:执行状态修改, 删除对话等)
然后就有了finally(),逻辑只放在一个地方,无论Promise运行成功还是失败,都会运行

new Promise((reslove, reject) => {
  // ...
}).then((res) => {
  // reslove
}).catch((err) => {
  // reject
}).finally(() => {
  // complete
});

异步迭代

es8中,可以使用async/await在同步的写法中执行异步函数,但是在循环中,循环依然保持同步,即在内部异步函数调用之前循环已经全部完成

async function foo(array) {
  for (let i of array) {
    await doSomething(i);
  }
}

es9引入了异步迭代器(asynchronous iterators),使await可以和for...of循环一起使用

async function foo (array) {
    for await (let i of array) {
        doSomething(i)
    }
}

ES2019/ES10

可选的catch

try {
    //...
} cath(unused) {
    // ...
}
//可以省略没有使用到的catch里的变量
try {
    //...
} catch {
    //...
}

Symbol.prototype.description

只读属性,返回Symbol对象的可描述的字符串

Symbol('desc').description // desc

Object.fromEntries(iterable)

将键值对列表转换为对象
Object.fromEntries() 执行与 Object.entries 互逆的操作:

//将对象转换为数组
const arr = Object.entries(obj) // [[key, value]]的形式

// 还原回来
const obj = Object.fromEntries(arr);

将map对象转为对象:

const map = new Map();
map.set('one', 1);
map.set('two', 2);

const obj = Object.fromEntries(map); // {one: 1, two: 2}

处理URL的查询字符串:

const paramsString = 'param1=foo&param2=baz';
const searchParams = new URLSearchParams(paramsString); // { param1: 'foo', param2: 'baz'}

String.prototype.{trimStart, trimEnd}

分别为去掉字符串左边和右边的空格,与trimLeft()和trimRight()相同
对于Web兼容性,trimLeft() 和trimRight() 将保留为trimStart() 和trimEnd() 的别名。

Array.prototype.{flat, faltMap}

flat([level])方法可以将多维数组展平成一维数组,且有空值时,会丢弃; 可选参数level指定展开几层

// 以前使用concat或reduce
const arr = ['a', 'b', ['c', 'd']];
const flattend = [].concat.apply([], arr);
// const flattend = [].concat(...arr);

flatMap()方法将map()和flat()组合成一个方法


const arr2 = [[7.1], [8.1], [9.1], [10.1], [11.1]];
//想要删除项目,只需返回[]
// do not include items bigger than 9
arr.flatMap(value => {
  if (value >= 10) {
    return [];
  } else {
    return Math.round(value);
  }
}); 
// [7, 8,9]

ES2020/ES11

String.prototype.matchAll(regexp)

返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器
regexp参数必须要设置全局模式g,否则会抛出异常
之前我们通过在循环中调用regexp.exec()来获取所有匹配项信息

const regexp = 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."
}

// matchAll
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."

import()

按需动态导入模块
在之前,根据需要导入模块时只能使用require()
@babel/preset-env中已经包含了@babel/plugin-syntax-dynamic-import,所有只需配置@babel/preset-env
import()返回的是一个Promise对象

// module.js
export default {
  name: 'shenjp'
}

// index.js
if (true) {
  let module = import('./module.js');
  console.log(module); // Promise {<pending>
  module.then(data => console.log(data)); // Module {default: {name: "shenjp"}, __esModule: true, Symbol(Symbol.toStringTag): "Module"}
}

import.meta

为当前运行的模块添加了一个特定host元数据对象

<script type="module" src="my-module.mjs"></script>
console.log(import.meta); // { url: "file:///home/user/my-module.mjs" }

BigInt:内置对象

可以表示任意大的整数,原本JavaScript只能表示2^53 -1以内的数字
定义一个BigInt:

// 1、在数字字面量后面加一个n
const big = 10n
// 2、调用BigInt()
const big2 = BigInt(9007199254740991) // 9007199254740991n
const big3 = BigInt('9007199254740991') // 9007199254740991n

typeof big2 // bigint

// 带小数的运算会被取整
5n/2n // 2n

注意: 不能用于Math对象中的方法;不能和Number实例混合运算,必须要转换为同一种类型才行(在BigInt转Number时,注意可能会丢失精度)
BigInt可以和+、*、-、**(次方)、%和除>>>(因为BigInt都是有符号的)之外的位操作

空值合并运算符(??)

只会左边的值严格等于null或undefined时才使用右边的值

'' || 'default' // default
'' ?? 'default' // ''
0 || 'defa' // defa
0 ?? 'defa' // defa

可选链

当我们在一个深的树形结构中查找一个属性值会像下面这样:

const name = data && data.info && data.info.name

//使用?.
const name1 = data?.info?.name
//还可以与空值合并运算符搭配使用
const name2 = data?.info?.name ?? 'jack'

// 其他使用
a?.b // a === null ? undefined : a.b
a?.[x] // a === null ? undefined : a[x]
a?.b() // a === null ? undefined : a.b()
a?.() // a === null ? undefined : a()
a?.b[3].c?.(x).d // a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d

// optional deletion
delete a?.b // a === null ? true : delete a.b

不支持可选链的:

1、可选的构造函数: new a?.()
2、可选的模块字符串: a?.`string`
3、可选的属性赋值: a?.b = c
4、可选的super: super?.(), super?.foo

globalThis

以前,在不同的JavaScript环境中获取全局对象需要不同的的语句,Web中可以通过window、self(Web workers)、frames取到全局对象,在Node.js中,使用global获得
现在可以统一使用globalThis来获取全局对象

Promise.allSettled()

返回一个在给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果

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
// rejected

ES2021/ES12

String.prototype.replaceAll(searchValue, replaceValue)

已有的String.prototype.replace只能替换第一个匹配的,如果需要匹配所有,需要使用正则如果加全局符合g
还可以使用split+join的方法达到替换所有
注意: replaceAll时,searchValue如果是正则但是是非全局时(即没有加g关键字),会引发异常,

Promise.any()

接收一个Promise可迭代对象,只要其中的一个promise成功,就返回那个已经成功的promise,如果没有一个promise成功,则返回一个失败的Promise
如果传入的参数是一个空的迭代对象,则会返回一个已失败状态的Promise
如果传入的参数不包含任何promise,则返回一个异步完成的promise
该方法会返回第一个成功的 promise 。只要有一个 promise 成功此方法就会终止,它不会等待其他的 promise 全部完成。

Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {
  // Any of the promises was fulfilled.
  console.log(first);
  // → 'home'
}).catch((error) => {
  // All of the promises were rejected.
  console.log(error);
});

Logical Assignment Operators(逻辑赋值操作符)

a ||= b // a || (a  = b)
a ??= b
a &&= b

数字分隔符(_)

在二进制、十六进制、BigInt中都可以使用

const x = 1000000000
const y = 1000_000_000
x === y // true

zhouing
4 声望2 粉丝