ES2023 来了,赶紧学起来
前言
ES6 是 2015 年提出的,按照这个逻辑 ES2023 应该叫做 ES14,为了避免混淆,我们就用年份来命名。回想上次关注 ES 标准,是不是还停留在 ES6?为了赶上 ES 标准迭代的步伐,我们一起来看看近几年又加入了哪些新特性。
注意:文中提到的年份是指该特性写入标准的年份。
ES2023
2023 年将会完成的提案
数组被修改时返回副本
Array 和 TypedArray 有很多方法(比如 sort/splice 等)会改变数组自身,比如:
const array = [3, 2, 1];
const sortedArray = array.sort();
// [1, 2, 3]
console.log(sortedArray);
// 原数组也变成了 [1, 2, 3]
console.log(array);
如果不希望改变数组自身,可以这样做:
const array = [3, 2, 1];
const sortedArray = array.toSorted();
// [1, 2, 3]
console.log(sortedArray);
// 原数组不变 [3, 2, 1]
console.log(array);
类似的方法还有这些:
T.prototype.toReversed() -> T
T.prototype.toSorted(compareFn) -> t
T.prototype.toSpliced(start, deleteCount, ...items) -> T
T.prototype.with(index, value) -> T
reverse/sort/splice 还能看懂,with 是干什么的?看个例子就明白了:
const array = [1, 2, 3];
const newArray = array.with(1, false);
// [1, false, 3]
console.log(newArray);
// 原数组不变 [1, 2, 3]
console.log(array);
WeakMap 支持 Symbol 作为 key
WeakMap 原本只支持 object 类型的 key,现在支持了 Symbol 类型作为 key。
const weak = new WeakMap();
weak.set(Symbol('symbol1'), {});
Hashbang 语法
Hashbang 也叫 Shebang,是一个由井号和叹号构成的字符序列 #!,用来指定使用哪种解释器执行此文件:
// hashbang.js
#!/usr/bin/env node
console.log('hashbang');
// nohashbang.js
console.log('no hashbang')
在终端执行,没有 Hashbang 时,需要使用 node 指令才能执行:
从尾部查找
涉及到两个函数 findLast / findLastIndex
const array = [1, 2, 3]
array.findLast(n => n.value % 2 === 1);
array.findLastIndex(n => n.value % 2 === 1);
ES2022
异常链
直接看示例,通过 err1.cause 可以拿到 err0 ;如果这个异常被重新抛出了很多次,那通过 err1.cause.cause.... 就能拿到所有相关的异常。
function willThrowError() {
try {
// do something
} catch (err0) {
throw new Error('one error', { cause: err })
}
}
try {
willThrowError()
} catch (err1) {
// 通过 err1.cause 就能拿到 err0 了
}
类静态代码块
静态代码块,用来实现复杂的静态属性/方法的初始化逻辑:
// 没有静态代码块时,初始化逻辑与类定义是分离开的
class C {
static x = ...;
static y;
static z;
}
try {
const obj = doSomethingWith(C.x);
C.y = obj.y
C.z = obj.z;
}
catch {
C.y = ...;
C.z = ...;
}
// 使用了静态代码块,代码逻辑更收敛
class C {
static x = ...;
static y;
static z;
static {
try {
// 这里的 this 代表 C 而不是 C 的实例
const obj = doSomethingWith(this.x);
this.y = obj.y;
this.z = obj.z;
}
catch {
this.y = ...;
this.z = ...;
}
}
}
还有个特殊的能力:访问私有属性
let getPrivateField;
class D {
#privateField;
constructor(v) {
this.#privateField = v;
}
static {
getPrivateField = (d) => d.#privateField;
}
}
getPrivateField(new D('private value'));
// → private value
Object.hasOwn
我们经常会看到这种用法,尤其是在开源库里:
let hasOwnProperty = Object.prototype.hasOwnProperty
if (hasOwnProperty.call(object, "foo")) {
console.log("has property foo")
}
如果使用 Object.hasOwn,可以简化成:
if (Object.hasOwn(object, "foo")) {
console.log("has property foo")
}
.at 返回指定索引的元素
可索引类型(String/Array/TypedArray)可以通过 at 来读取指定索引的元素,而且支持传入负数
// 返回 4
[1, 2, 3, 4].at(-1)
判断私有属性是否存在
可以通过 in 关键字来判断
class C {
#brand = 0;
static isC(obj: C) {
return #brand in obj;
}
}
顶层 await
不需要 async 包裹,也可以使用 await,比如:
function fn() {
return Promise.resolve();
}
// 以前需要通过 IIFE 实现
(async function () {
await fn();
})();
// 支持顶层 await 后,可以直接调用
await fn();
正则表达式切片
通过正则匹配,我们只能拿到匹配到的字符串,通过切片可以拿到这些字符串在原字符串的位置
const re1 = /a+(z)?/d;
const s1 = "xaaaz";
const m1 = re1.exec(s1);
ES2021
数值分隔符
如果数字比较长,可读性就比较差,通过分隔符 _ 可以提高可读性:
// 1000000
console.log(1_000_000)
// 分隔符只要不是数字的第一位和最后一位,放到任何位置都是有效的
1_000000000_1_2_3
逻辑运算符赋值
和算术运算符赋值是类似的,但是逻辑运算符还会存在短路能力,可以对比看一下:
// 算数运算符
let x = 1;
// x 是 2
x += 1;
// 逻辑运算符
let x = 1;
// x 依然是 1,因为 x 是真值,所以被短路,相当于 x = 1 || 3
x ||= 3;
WeakRef
弱引用,可以引用某个对象,而且不会阻止该对象被垃圾回收。
let obj = { name: 'obj1' };
let weakRef = new WeakRef(obj);
// 获取引用的对象
// 如果引用对象被回收,拿到的就是 undefined
weakRef.deref();
Promise.any
和其他几个函数做个对比。
Promise.any
只要有一个输入 resolve 了,那么它就会 resolve:
Promise.any([
Promise.reject('reject 1'),
Promise.reject('reject 2'),
Promise.reject('reject 3'),
Promise.resolve('1'),
Promise.resolve('2'),
]).then(
first => {
// 只要有一个 resolve,就会执行到这里
// 打印的是 1
console.log(first);
},
error => {
// 所有都 reject 时,才会走到这里
console.log(error);
},
);
Promise.allSettled
无论输入的 promise 是 reject 还是 resolve,都会汇总给 then
Promise.allSettled([
Promise.resolve('1'),
Promise.resolve('2'),
Promise.reject('e1'),
Promise.resolve('3'),
Promise.resolve('4'),
])
.then(res => {
console.log('then', res);
});
Promise.all
只要有一个 reject,那么就会 reject。下面的示例会走到 catch:
Promise.all([
Promise.resolve('1'),
Promise.resolve('2'),
Promise.reject('e1'),
Promise.resolve('3'),
])
.then(res => {
console.log('then', res);
})
.catch(err => {
// 打印 catch e1
console.log('catch', err);
});
Promise.race
如果有一个 reject(或 resolve)了,那么就会 reject(或 resolve)
function delay(type: 'resolve' | 'reject', timeout: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (type === 'reject') {
reject(type);
} else {
resolve(type);
}
}, timeout);
});
}
// 打印 then resolve
Promise.race([delay('resolve', 1000), delay('reject', 2000)])
.then(res => {
console.log('then', res);
})
.catch(err => {
console.log('catch', err);
});
// 打印 catch reject
Promise.race([delay('resolve', 2000), delay('reject', 1000)])
.then(res => {
console.log('then', res);
})
.catch(err => {
console.log('catch', err);
});
String.prototype.replaceAll
替换所有匹配的字符串
// 12c12d12
'abcabdab'.replaceAll('ab', '12')
// 12cabdab
'abcabdab'.replace('ab', '12')
ES2020
import.meta
提供了与宿主相关的模块元信息。
空值合并运算符
只有在左操作数为 null 或 undefined 时,才会返回右操作数
// "default"
let a = undefined || "default"
// "default"
let a = undefined ?? "default"
// "default"
let a = null || "default"
// "default"
let a = null ?? "default"
// "default"
let a = "" || "default"
// ""
let a = "" ?? "default"
// "default"
let a = 0 || "default"
// 0
let a = 0 ?? "default"
可选链
直接看代码
// 不用可选链
const street = user.address && user.address.street;
// 用可选链
const street = user.address?.street
BigInt
可以表示大于2的53次方(js Number 类型可表示的最大数字)的数字
// 数字尾部加个 n
const theBiggestInt = 9007199254740991n;
// 或者直接调 BigInt 构造
const alsoHuge = BigInt(9007199254740991);
import()
运行时动态加载模块。
String.prototype.matchAll
match 只能返回匹配的字符串,但是无法返回组信息;matchAll 可以返回匹配的字符串,以及组信息。
总结
ES2016~ES2019 就不一一列举了,大家应该都比较熟悉了。如果有不清楚的特性,欢迎留言交流。
参考
欢迎一起交流。
转载自:https://juejin.cn/post/7206302271079989308