ES12 (ES2021)
ES2021(ES12)引入了一系列实用的新特性,包括逻辑赋值运算符、字符串替换增强、新的Promise方法和错误类型、数字可读性改进以及国际化API扩展。这些更新使JavaScript代码更加简洁、可读,并提供了更强大的异步操作和错误处理能力。
ES2021 (ES12)的新特性如下:
- 逻辑赋值运算符 (&&=, ||=, ??=)
- String.prototype.replaceAll( )
- AggregateError
- Promise.any( )
- 数字分隔符
- WeakRefs 和 FinalizationRegistry
- Intl.ListFormat
- Intl.DateTimeFormat 新选项
1. 逻辑赋值运算符 (&&=, ||=, ??=)
逻辑赋值运算符是三种新的运算符,它们结合了逻辑运算符 (&&, ||, ??) 和赋值操作,使得代码更加简洁。
-
&&=:仅当左操作数为真值时,才对其进行赋值。
let user = {
isLogin: true,
};
user.isLogin &&= "admin";
console.log(user.isLogin); // admin -
||=:仅当左操作数为假值时,才对其进行赋值。
let setting = {
theme: "",
};
// 只有 setting.theme 为假值时才会赋值
setting.theme ||= "dark";
console.log(setting.theme); // dark -
??=:仅当左操作数为 null 或 undefined 时,才对其进行赋值。
let x = undefined;
x ??= "default";
console.log(x); // default
2. String.prototype.replaceAll( )
replaceAll( ) 方法允许你替换字符串中 所有 匹配项,而不需要手动使用正则表达式或循环。
之前没有 repalceAll( ) 的时候,需要手动使用正则:
const str = "hello world, hello javascript";
const newstr = str.replace(/hello/g, "hi");
console.log(newstr);
或者使用循环:
const str = "hello world, hello javascript";
let newstr = str;
while (newstr.includes("hello")) {
newstr = newstr.replace("hello", "hi");
}
console.log(newstr)
现在可以直接使用 repalceAll( ) 来简化操作:
const str = "hello world, hello javascript";
console.log(str.replaceAll("hello", "hi"));
3. AggregateError
AggregateError 是 ES2021 引入的一种新的错误类型,它用于表示多个错误的集合。它通常与 Promise.any( ) 一起使用,当所有的 Promise 都被拒绝时,AggregateError 可以包含这些 Promise 被拒绝的原因。
AggregateError 是从 Error 类继承而来的,所以它具有 Error 类的标准特性,如 message 和 stack。
在实例化 AggregateError 时,它接收两个参数:
- errors: 一个可迭代对象(如数组),其中包含多个错误的错误原因。
- message: 一个字符串,描述这个错误的概述信息。这个参数是可选的。
const aggreErrors = new AggregateError(
[new Error("请求API出错"), new Error("数据库连接错误")],
"发生了多个错误"
);
console.log(aggreErrors.message);
console.log(aggreErrors.errors);
4. Promise.any( )
Promise.any( ) 接受一组 Promise,只要有一个 Promise 成功,它就会立即返回这个结果。如果所有 Promise 都失败,它会返回一个失败的 AggregateError,其中包含所有拒绝的原因。
成功示例:
const p1 = Promise.reject(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.any([p1, p2, p3])
.then((result) => {
console.log(result); // 2
})
.catch((err) => {
console.log(err);
});
失败示例:
const p1 = Promise.reject(1);
const p2 = Promise.reject(2);
const p3 = Promise.reject(3);
Promise.any([p1, p2, p3])
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
// [AggregateError: All promises were rejected] { [errors]: [ 1, 2, 3 ] }
});
实际应用场景:在开发 Web 应用时,通常会把一些静态资源(如 JS 文件、图片、CSS 文件等)存放在多个 CDN 上,以提高资源加载的可靠性。如果某个 CDN 不可用,可以尝试从其他的 CDN 获取资源。使用 Promise.any( ),你可以确保只要有一个 CDN 能提供资源,应用就能继续运行。
// 模拟从多个 CDN 加载 jQuery
const loadFromCDN1 = () =>
new Promise((resolve, reject) =>
setTimeout(() => reject("CDN 1 failed"), 1000)
);
const loadFromCDN2 = () =>
new Promise((resolve) =>
setTimeout(() => resolve("CDN 2: Loaded jQuery"), 1500)
);
const loadFromCDN3 = () =>
new Promise((resolve) =>
setTimeout(() => resolve("CDN 3: Loaded jQuery"), 2000)
);
Promise.any([loadFromCDN1(), loadFromCDN2(), loadFromCDN3()])
.then((value) => {
console.log(value);
init();
})
.catch((err) => {
console.log(err);
// [AggregateError: All promises were rejected] { [errors]: [ 'CDN 1 failed' ] }
});
function init() {
console.log("jQuery is ready");
}
5. 数字分隔符
数字分隔符(下划线 _)用于使大型数字更加可读,特别是在处理金融数据或长整型数值时。
const largeNum = 1_000_000_000;
console.log(largeNum); // 1000000000
6. WeakRefs 和 FinalizationRegistry
WeakRefs是 ES2021 引入的一项特性,它允许你创建对对象的 弱引用,不会阻止该对象被垃圾回收机制回收。
-
普通引用:在普通引用的情况下,只要存在对对象的引用,垃圾回收器就不会回收这个对象
let obj = {
name: '张三'
}
// 创建了一个 obj 对象的引用
const objRef = obj;
// 虽然将 obj 设置为了 null,但是 objRef 仍然引用着这个对象
// 所以垃圾回收器不会回收该对象
obj = null; -
弱引用:不会阻止对象被垃圾回收,通过 WeakRef,你可以检查对象是否还存在,并在对象还没有被回收的情况下进行访问
let obj = {
name: "张三",
};
// 引用 obj
// const objRef = obj;
// 相当于创建了一个 obj 的弱引用
const objWeakRef = new WeakRef(obj);
// console.log(objRef); // 通过强引用访问
console.log(objWeakRef.deref()); // 通过弱引用访问
obj = null; // 释放 obj 的引用
// 释放了 obj 之后,垃圾回收器就会回收 obj
// 有可能为 undefined,这个取决于垃圾回收器何时回收 obj
console.log(objWeakRef.deref()); // undefined
实际应用场景示例:在一些前端框架中,可能会缓存某些 DOM 元素以加快访问速度,但是这些 DOM 元素不应长期占用内存。通过 WeakRef,可以缓存这些 DOM 节点,当它们被移除时,不会阻止垃圾回收器回收。
class DOMCache {
constructor() {
this.cache = new Map();
}
addCache(id, node) {
// 这里在添加缓存节点的时候,使用 WeakRef 创建弱引用
this.cache.set(id, new WeakRef(node));
}
getCache(id) {
const ref = this.cache.get(id);
if (ref) {
const node = ref.deref(); // 获取到弱引用的DOM节点
if (node) {
return node;
}
// 如果没有进入上面的分支,说明节点已经被回收
this.cache.delete(id);
}
return null;
}
}
const domCache = new DOMCache();
let myDiv = document.createElement("div"); // 创建DOM节点
myDiv.id = "myDiv";
// 缓存DOM节点
domCache.addCache("myDiv", myDiv);
// 之后可以从缓存中获取DOM节点
domCache.getCache("myDiv");
myDiv = null; // 释放DOM节点的引用
// 释放掉之后,垃圾回收器就会在一定的时间内回收DOM节点
FinalizationRegistry 也是和垃圾回收相关的新特性,它能帮助开发者管理和清理不再需要的对象资源。它允许在对象被垃圾回收后执行某些回调逻辑,从而提供一种有效的方式来清理外部资源,例如文件句柄、网络连接、缓存条目等。
使用步骤:
-
创建 FinalizationRegistry 实例
// 对象被垃圾回收器回收的时候,就会执行该回调函数
const registry = new FinalizationRegistry((xxx)=>{
// 回调函数中根据传入的参数,做一些释放的操作
}) -
注册对象:使用 register( ) 方法,将某个对象与外部资源(例如文件句柄、网络连接等)关联起来。
registry.register(obj, heldValue);- obj 是要注册的目标对象。
- heldValue 是与该对象相关联的资源,在对象被回收时,FinalizationRegistry 将通过回调函数访问它。
实际应用场景举例:
假设有一个数据库连接池,每次创建数据库连接时,希望在连接对象被垃圾回收后自动关闭该连接,以避免连接泄漏。
class DatabaseConnection {
constructor(name) {
this.name = name;
this.isConnected = true;
console.log(`已连接至 ${this.name} 数据库`);
}
// 关闭数据库连接
close() {
this.isConnected = false;
console.log(`已关闭 ${this.name} 数据库`);
}
}
const databaseConnection = new DatabaseConnection("MongoDB");
const reg = new FinalizationRegistry((heldValue) => {
heldValue.close();
});
// 需求:当 databaseConnection 实例被垃圾回收时,自动关闭数据库连接
databaseConnection = null;
reg.register(databaseConnection, databaseConnection);
WeakRef 和 FinalizationRegistry 这两种同时在 ES2021 引入的新工具,通常都是用于优化内存管理的,特别是在处理大型对象或不应阻止垃圾回收的对象时。虽然它们可以独立使用,但在某些场景下,它们经常 结合使用,提供更好的内存和资源管理。
这两个工具各司其职:
- WeakRef:提供了对对象的弱引用,允许对象在没有其他强引用时被垃圾回收,而不会因为某个引用继续存在而阻止对象回收。
- FinalizationRegistry:当对象被垃圾回收时,允许开发者执行一些清理操作,比如释放外部资源(文件句柄、网络连接等)。
实际应用场景举例:一个缓存系统,缓存的数据对象需要能够自动释放以防止内存泄漏。
- 通过 WeakRef,你可以持有对象的弱引用
- 通过 FinalizationRegistry,你可以在对象被垃圾回收时自动清理缓存
class Cache {
constructor() {
// 负责存储缓存数据
this.cache = new Map();
this.reg = new FinalizationRegistry((key) => {
// 当缓存数据被回收时,会调用这个回调函数
console.log(`清除缓存:${key}`);
this.cache.delete(key);
});
}
// 添加缓存
add(key, value) {
this.cache.set(key, new WeakRef(value));
// 注册缓存数据
this.reg.register(value, key);
}
// 获取缓存的值
get(key) {
const ref = this.cache.get(key);
if (ref) {
const value = ref.deref(); // 获取到弱引用的DOM节点
if (value) {
return value;
}
}
return null;
}
}
7. Intl.ListFormat
Intl.ListFormat 是 ES2021 引入的国际化 API,用于根据语言环境来格式化列表。它可以按照指定的样式和格式,将多个项以符合目标语言的方式呈现,适用于输出并列的列表信息(如物品列表、姓名列表等),并自动处理连接词(如 "and"、"or")和列表格式。
语法:
const listFormatter = new Intl.ListFormat(locale, options);
- locale:指定要使用的语言或区域(如 'en' 表示英语,'fr' 表示法语)。
- options:配置对象
- style:列表格式的样式,可以是 'long'(默认,长格式),'short'(短格式),或 'narrow'(紧凑格式)。
- type:定义项目间的关系,可以是 'conjunction'(连接,默认),'disjunction'(分离,如 "or"),或 'unit'(单位)。
const javaScriptFrameworks = ["React", "Vue", "Angular"];
// 相当于创建了一个国际化的格式化类型
const shortUnitFormat = new Intl.ListFormat("en", {
style: "short",
type: "unit",
});
console.log(shortUnitFormat.format(javaScriptFrameworks));
const narrowUnitFormatter = new Intl.ListFormat("en", {
style: "narrow",
type: "unit",
});
console.log(narrowUnitFormatter.format(javaScriptFrameworks));
// Prints 'Angular React Vue'
const longConjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
console.log(longConjunctionFormatter.format(javaScriptFrameworks));
// Prints 'Angular, React, and Vue'
const longDisjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "disjunction",
});
console.log(longDisjunctionFormatter.format(javaScriptFrameworks));
// Prints 'Angular, React, or Vue'
const longConjunctionItalianFormatter = new Intl.ListFormat("it", {
style: "long",
type: "conjunction",
});
console.log(longConjunctionItalianFormatter.format(javaScriptFrameworks));
// Prints 'Angular, React e Vue'
const longDisjunctionGermanFormatter = new Intl.ListFormat("de", {
style: "long",
type: "disjunction",
});
console.log(longDisjunctionGermanFormatter.format(javaScriptFrameworks));
// Prints 'Angular, React oder Vue'
8. Intl.DateTimeFormat 新选项
Intl.DateTimeFormat 是 JS 的国际化 API,用于格式化日期和时间。ES2021 中引入了两个新的选项:
- dateStyle:用于指定日期显示格式
- full:完整格式,包含星期、月、日和年份。
- long:不显示星期,但仍然是全月名和年份。
- medium:月名简写,显示日和年份。
- short:仅数字形式的日期。
- timeStyle:用于指定时间的显示格式
- full:完整格式,包括小时、分钟、秒以及时区。
- long:简化时区信息,显示当地时区简称。
- medium:仅显示小时、分钟和秒。
- short:最简洁的形式,只显示小时和分钟。
使用 dateStyle 和 timeStyle,开发者可以更方便地处理国际化的日期和时间格式,特别是当你希望根据不同的语言和区域提供符合用户习惯的显示方式时。这简化了过去需要手动设置 year、month、day 等属性的方式,并为不同的语言提供自动调整。
格式化日期(dateStyle)示例:
const d = new Date();
// 创建一种格式化风格
const formatStyle = new Intl.DateTimeFormat("en-US", {
dateStyle: "short",
});
console.log(formatStyle.format(d));
格式化时间(timeStyle)示例:
const date = new Date();
const timeFormatterFull = new Intl.DateTimeFormat("en-US", {
timeStyle: "full",
});
console.log(timeFormatterFull.format(date)); // 输出: "12:03:55 PM China Standard Time"
const timeFormatterLong = new Intl.DateTimeFormat("en-US", {
timeStyle: "long",
});
console.log(timeFormatterLong.format(date)); // 输出: "12:03:55 PM GMT+8"
const timeFormatterMedium = new Intl.DateTimeFormat("en-US", {
timeStyle: "medium",
});
console.log(timeFormatterMedium.format(date)); // 输出: "12:03:55 PM"
const timeFormatterShort = new Intl.DateTimeFormat("en-US", {
timeStyle: "short",
});
console.log(timeFormatterShort.format(date)); // 输出: "12:03 PM"
同时格式化日期和时间示例:
const date = new Date();
const formatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
console.log(formatter.format(date)); // 输出: "Sep 12, 2024, 12:04 PM"
Intl.DateTimeFormat 会根据你提供的语言和区域自动调整格式。
例如,日期在美国(en-US)的显示方式与在法国(fr-FR)的方式会有所不同:
const date = new Date();
const formatterFr = new Intl.DateTimeFormat("fr-FR", {
dateStyle: "full",
timeStyle: "long",
});
console.log(formatterFr.format(date)); // 输出: "jeudi 12 septembre 2024 à 12:06:15 UTC+8"
-EOF-