ES11 (ES2020)
ES2020(ES11)推出了一些新的特性,这些特性增强了 JS 对异步编程、模块化、对象访问等方面的支持以及简化某些操作。从 BigInt 到可选链操作符,这些更新为开发者提供了更强大的工具和更简洁的语法。
ES2020(ES11)推出了一些新的特性,这些特性增强了 JS 对异步编程、模块化、对象访问等方面的支持以及简化某些操作。
- BigInt
- Dynamic import( )
- globalThis
- Nullish Coalescing Operator (??)
- Optional Chaining (?.)
- Promise.allSettled( )
- for-in Loop Order Guarantee
- import.meta
- String.prototype.matchAll( )
- Module Namespace Exports
1. BigInt
基础数据类型:null、undefined、string、number、boolean、symbol、bigint
BigInt 是一种新的基础数据类型,用于表示任意精度的整数。它允许你处理比 Number 类型更大的整数。当需要处理超出 Number 类型安全范围(即 253 - 1)的整数时非常有用。
BigInt 字面量的表示是在一串数字后面跟小写字母 n,默认情况下基数为 10,但是可以通过前缀 0b、0o 和 0x 来表示二、八、十六进制
1234n
0b10110010n
0o777n
0x800AFn
可以用 BigInt( ) 方法将常规 JS 数值或者字符串转换为 BigInt 值:
console.log(BigInt(Number.MAX_SAFE_INTEGER));
console.log(2 ** 53 - 1);
let str = "1" + "0".repeat(10);
console.log(BigInt(str));
BigInt 值的算数计算和常规 JS 数值的算数计算类似,不过 除法会丢弃余数并且会向下舍入:
console.log(5 / 2); // 2.5
console.log(5n / 2n); // 2n 将余数舍弃掉了
不能混用 BigInt 操作数和常规数值操作数
console.log(100 + 100n);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
不过允许 BigInt 操作数和常规数值做比较运算:
console.log(3 > 2n); // true
最后注意,Math 的任何方法均不接受 BigInt 操作数:
Math.pow(2n, 3);
// TypeError: Cannot convert a BigInt value to a number
2. Dynamic import( )
import( ) 语法允许按需动态加载模块。这对于按需加载依赖项或在特定条件下加载模块非常有用。
比如例如根据用户的行为、某个特定的事件触发、或者在运行时根据条件加载模块:
async function loadChartLibrary() {
if (conditionToLoadChart) {
// 根据conditionToLoadChart条件来判断是否要加载图表库
const { Chart } = await import("chart.js");
const chart = new Chart(ctx, {
/* chart configuration */
});
}
}
在没有动态导入这个特性之前,JS 使用同步导入,所有模块会在应用启动时一并加载。
// main.js
import HomeView from './views/HomeView.vue';
import AboutView from './views/AboutView.vue';
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView }
];
有了动态导入这个特性后,可以按需加载模块,这意味着只有当用户访问某个特定路由时,才会加载对应的组件。
// main.js
const routes = [
{ path: '/', component: () => import('./views/HomeView.vue') },
{ path: '/about', component: () => import('./views/AboutView.vue') }
];
3. globalThis
globalThis 是一个跨平台的全局对象,统一了不同环境下的全局对象访问方式。
- 在浏览器中,它等同于 window
- 在 Node.js 中等同于 global
4. Nullish Coalescing Operator (??)
?? 运算符是一个逻辑运算符,当左侧操作数为 null 或 undefined 时,返回右侧操作数。否则,返回左侧操作数。它与逻辑或运算符||很相似,但它更精确,因为 ?? 只针对 null 和 undefined 进行处理,而||会把其他"假值"(如 false、0、'')也视为无效。
使用或逻辑运算符:||会将所有的"假值"(false、0、''、null、undefined)视为无效并返回默认值。
console.log(0 || 10); // 10
console.log('' || 10); // 10
console.log(false || 10); // 10
console.log(undefined || 10); // 10
console.log(null || 10); // 10
使用 ?? 逻辑运算符:只处理 null 和 undefined,不影响其他的"假值"。
console.log(0 ?? 10); // 0
console.log('' ?? 10); // ''
console.log(false ?? 10); // false
console.log(undefined ?? 10); // 10
console.log(null ?? 10); // 10
5. Optional Chaining (?.)
可选链操作符 ?. 允许你安全地访问对象嵌套的属性或调用方法,即使其中某些属性可能不存在,不会抛出错误,而是返回 undefined。
const user = {
address: {
street: "123 Main St",
},
};
console.log(user?.address?.street);
console.log(user?.contact?.email); // undefined
console.log(user.contact.email);
// TypeError: Cannot read properties of undefined (reading 'email')
优势:避免了手动检查每个嵌套属性是否存在的麻烦,简化了对深层嵌套对象的访问。
6. Promise.allSettled( )
Promise.allSettled( ) 方法返回一个在所有给定的 Promise 都已经解决或拒绝后解决的 Promise。它不会因为某个 Promise 被拒绝而导致整个处理失败,适合处理所有结果后再执行下一步逻辑。
const promises = [
Promise.resolve(1),
Promise.reject("Error"),
Promise.resolve(3),
];
Promise.allSettled(promises).then((results) => {
console.log(results);
// 输出: [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'Error'}, {status: 'fulfilled', value: 3}]
});
优势:适用于需要等待所有 Promise 都完成(无论是否成功)后再处理的场景。
实际应用场景示例:假设开发一个仪表盘,需要从多个服务获取数据:
- 用户个人信息(通过用户服务获取)
- 天气信息(通过天气API获取)
- 消息通知(通过通知服务获取)
有时候,某些服务可能会失败,但我们依然希望展示能够获取到的数据,比如即使天气 API 失败了,但是我们希望显示用户信息和消息通知。这时,Promise.allSettled( ) 就非常合适,它可以确保所有请求无论是成功还是失败,都会返回结果。
// 模拟API请求
const fetchUserProfile = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
resolve({ id: 1, name: "Alice" });
}, 1000)
);
const fetchWeatherData = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
reject("天气API请求失败");
}, 3000)
);
const fetchNotifications = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
resolve([{ id: 1, message: "新消息" }]);
}, 500)
);
Promise.allSettled([
fetchUserProfile(),
fetchWeatherData(),
fetchNotifications(),
]).then((results) => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index + 1} 成功`, result.value);
} else {
console.log(`Promise ${index + 1} 失败`, result.reason);
}
});
// 拼接拿到的结果
const userProfile =
results[0].status === "fulfilled" ? results[0].value : null;
const weather =
results[1].status === "fulfilled" ? results[1].value : "无法获取天气信息";
const notifications =
results[2].status === "fulfilled" ? results[2].value : [];
// 模拟在仪表盘显示数据
console.log("用户信息:", userProfile);
console.log("天气信息:", weather);
console.log("通知:", notifications);
});
7. for-in Loop Order Guarantee
在 ES2020 之前:for-in 的遍历顺序不固定,不同引擎可能有不同的实现。在 ES2020 中,for-in 的遍历顺序被标准化,循环遍历对象的属性时,遍历顺序为:
- 首先遍历整数属性
- 然后遍历字符串键
- 最后遍历符号属性
const obj = {
2: "Second", // 整数键
1: "First", // 整数键
b: "Letter B", // 字符串键
a: "Letter A", // 字符串键
[Symbol("symbolKey")]: "Symbol Key", // 符号键
};
for (let key in obj) {
console.log(key);
}
/*
1
2
b
a
*/
- 整数类型的键,会按照升序进行排列
- 字符串键,会按照对象里面定义的顺序依次输出
思考🤔:为什么符号类型的键没有输出?
答案:虽然在 ES2020 标准中,规划的顺序是数字、字符串、符号,但是 for-in 只能枚举对象中可枚举的键,符号类型的键不属于可枚举范畴,换句话说,符号类型不会被 for-in 枚举。如果想要访问符号类型的键,Object.getOwnPropertySymbols( )
8. import.meta
import.meta 是 ES 模块的一部分,它为开发者提供了一种访问当前模块元数据的方法。import.meta 是一个只读对象,它包含当前模块的特定信息。不同的 JS 环境可能会为 import.meta 提供不同的信息,取决于它在何种上下文中运行(如浏览器或 Node.js)
- 在浏览器环境中,import.meta 包含了 url 属性,它是一个指向当前模块的绝对 URL。
- 在 Node.js 环境中,import.meta 也可以包含模块特定的元数据信息,但它的具体属性取决于该环境的实现。
<body>
<script type="module" src="./index.js"></script>
</body>
console.log(import.meta);
// {url: 'http://127.0.0.1:5500/index.js', resolve: ƒ}
import.meta.url 是最常见的属性,它可以用于动态计算相对文件路径,加载文件资源。
示例一:Vite 中针对别名的配置:
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
示例二:Vite 中访问环境变量
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// ...
]
})
9. String.prototype.matchAll( )
String.prototype.matchAll( ) 是 ES2020 引入的一个非常有用的功能,它允许你对字符串进行全局正则匹配,并返回包含所有的匹配结果的迭代器(包括捕获组),而不仅仅是第一个匹配。它特别适合在正则表达式中使用全局(g)标志时,提取多个匹配结果。
基础示例:假设我们想要从一个字符串中提取出所有单词以及它们的起始位置
const text = "Hello world! Welcome to JavaScript.";
const reg = /\b(\w+)\b/g;
const matches = text.matchAll(reg);
console.log(matches);
for (const m of matches) {
console.log(m);
}
实际场景示例:提取文本中的所有 URL、协议、域名
const text = `
Visit us at https://www.example.com or follow our blog at http://blog.example.com!
You can also check https://support.example.com for more information.
`;
// 更精确的正则表达式:提取完整的 URL,包括域名和路径
const regex = /(https?):\/\/([^\s/$.?#].[^\s!]*)/g;
text.matchAll(regex).forEach((m) => {
console.log(`完整的 URL: ${m[0]}`); // 完整的匹配结果
console.log(`协议: ${m[1]}`); // 捕获的协议 (http 或 https)
console.log(`域名: ${m[2]}`); // 捕获的域名部分
console.log("\n"); // 换行
});
10. Module Namespace Exports
ES2020 为模块导出添加了对命名空间导出的标准化,使用命名空间导出可以将一个模块的所有导出重新打包并导出给另一个模块。
// 在 module.js 中
export const a = 1;
export const b = 2;
// 在 main.js 中
import * as moduleNamespace from "./module.js";
console.log(moduleNamespace.a); // 1
console.log(moduleNamespace.b); // 2
-EOF-