ES11(ES2020)新特性

发布时间:2020年6月 ES11 是一个重要版本,新增了空值合并、可选链、BigInt、动态导入等特性。


1. 可选链运算符(Optional Chaining)?.

安全地访问深层嵌套的属性,遇到 nullundefined 时短路返回 undefined,不报错。

基本用法

js 复制代码
let user = {
  name: '张三',
  address: {
    city: '北京',
    street: '长安街'
  }
};

// 旧写法
let city = user && user.address && user.address.city;

// 新写法
let city = user?.address?.city;     // '北京'
let zip = user?.address?.zip;       // undefined(不会报错)
let phone = user?.contact?.phone;   // undefined

可选链调用方法

js 复制代码
let obj = {
  method() { return 42; }
};

obj.method?.();     // 42
obj.otherMethod?.(); // undefined(不会报错)

可选链访问数组元素

js 复制代码
let arr = null;
let item = arr?.[0];      // undefined
let item2 = arr?.[0]?.name; // undefined

注意事项

js 复制代码
// 可选链不能用于赋值
obj?.name = '李四';   // SyntaxError

// 如果前面的值为 null/undefined,后面的表达式不会执行
let a = null;
let b = a?.foo.bar.baz();  // undefined,foo.bar.baz() 不会执行

实际应用

js 复制代码
// 从 API 返回的数据中安全取值
let data = response?.data?.list?.[0]?.name;

// DOM 操作
let value = document.querySelector('#input')?.value;

// React/Vue 中
let userName = this.props?.user?.name ?? '匿名用户';

2. 空值合并运算符(Nullish Coalescing)??

只在值为 nullundefined 时使用默认值(|| 会在值为 0''false 时也触发)。

基本用法

js 复制代码
// || 的问题:0、''、false 都会被当作假值
let count = 0;
console.log(count || 10);    // 10(错误!0 被当成假值)
console.log(count ?? 10);    // 0(正确!只有 null/undefined 才用默认值)

let name = '';
console.log(name || '匿名');  // '匿名'(错误!空字符串被覆盖)
console.log(name ?? '匿名');  // ''(正确!空字符串是有效值)

与 || 的对比

js 复制代码
0 ?? 100;          // 0
0 || 100;          // 100

'' ?? 'default';   // ''
'' || 'default';   // 'default'

false ?? true;     // false
false || true;     // true

null ?? 'fallback';  // 'fallback'
null || 'fallback';  // 'fallback'

undefined ?? 'x';    // 'x'
undefined || 'x';    // 'x'

实际应用

js 复制代码
// 设置默认值
let port = config.port ?? 3000;
let host = config.host ?? 'localhost';
let debug = config.debug ?? false;

// 与可选链组合使用
let name = user?.profile?.name ?? '匿名用户';
let count = list?.length ?? 0;

注意:不能与 || 和 && 混用

js 复制代码
// 语法错误
null ?? 'default' || 'other';  // SyntaxError

// 需要加括号
(null ?? 'default') || 'other';  // 'default'

3. BigInt(大整数)

表示任意精度的整数,突破 Number.MAX_SAFE_INTEGER(2^53 - 1)的限制。

创建 BigInt

js 复制代码
// 方式1:数字后加 n
let big1 = 9007199254740993n;

// 方式2:BigInt() 函数
let big2 = BigInt(9007199254740993);
let big3 = BigInt('9007199254740993');

解决精度问题

js 复制代码
// Number 的精度限制
9007199254740992 === 9007199254740993;  // true(精度丢失!)

// BigInt 没有精度限制
9007199254740992n !== 9007199254740993n; // true

运算

js 复制代码
let a = 12345678901234567890n;
let b = 98765432109876543210n;

a + b;   // 111111111011111111100n
a - b;   // -86419753208641975320n
a * b;   // 1219326311370217952237463801111263526900n
a / b;   // 0n(BigInt 除法向下取整)
a % b;   // 12345678901234567890n

// 比较运算
10n > 5;          // true
10n === 10;       // false(类型不同)
10n == 10;        // true(宽松相等)

注意事项

js 复制代码
// BigInt 不能与 Number 混合运算
10n + 5;    // TypeError

// 需要先转换
Number(10n) + 5;  // 15
BigInt(5) + 10n;  // 15n

// 不能用 Math 方法
Math.max(1n, 2n);  // TypeError

// JSON 不支持 BigInt
JSON.stringify({ a: 1n });  // TypeError

4. Promise.allSettled()

等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的结果:

基本用法

js 复制代码
let p1 = Promise.resolve('成功1');
let p2 = Promise.reject('失败');
let p3 = Promise.resolve('成功2');

Promise.allSettled([p1, p2, p3]).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log('成功:', result.value);
    } else {
      console.log('失败:', result.reason);
    }
  });
});
// 成功: 成功1
// 失败: 失败
// 成功: 成功2

与 Promise.all 的区别

js 复制代码
// Promise.all:任一失败就整体失败
Promise.all([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log('有失败的:', err));
// 输出:有失败的: 失败

// Promise.allSettled:全部完成后才返回,包含每个结果
Promise.allSettled([p1, p2, p3])
  .then(res => console.log(res));
// [
//   { status: 'fulfilled', value: '成功1' },
//   { status: 'rejected', reason: '失败' },
//   { status: 'fulfilled', value: '成功2' }
// ]

实际应用

js 复制代码
// 批量请求,关心所有结果
async function fetchAll(urls) {
  let results = await Promise.allSettled(
    urls.map(url => fetch(url).then(r => r.json()))
  );
  
  let succeeded = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
  
  let failed = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
  
  console.log(`成功: ${succeeded.length}, 失败: ${failed.length}`);
  return { succeeded, failed };
}

5. 动态导入 import()

运行时按需加载模块,返回 Promise:

基本用法

js 复制代码
// 静态导入:编译时加载,必须写在顶部
import { module } from './module.js';

// 动态导入:运行时按需加载
let module = await import('./module.js');

按需加载

js 复制代码
// 点击按钮时才加载
button.addEventListener('click', async () => {
  let { Chart } = await import('./chart.js');
  new Chart(canvas, config);
});

条件加载

js 复制代码
async function loadPolyfill() {
  if (!window.Promise) {
    await import('promise-polyfill');
  }
}

路由懒加载

js 复制代码
// React 路由懒加载
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));

注意

  • 动态导入返回模块的命名空间对象
  • 可以在普通脚本中使用(不限于模块脚本)

6. globalThis

统一的全局对象,在不同环境下指向正确的全局对象:

js 复制代码
// 不同环境的全局对象不同:
// 浏览器中:window
// Web Worker 中:self
// Node.js 中:global

// ES11 提供统一的 globalThis
console.log(globalThis);  // 浏览器中指向 window

实际应用

js 复制代码
// 兼容写法(旧)
let globalObj = typeof window !== 'undefined' ? window
  : typeof global !== 'undefined' ? global
  : typeof self !== 'undefined' ? self : {};

// ES11 简化
let globalObj = globalThis;

7. String.prototype.matchAll()

返回字符串中所有匹配正则表达式的迭代器:

基本用法

js 复制代码
let str = 'test1test2test3';
let matches = str.matchAll(/t(e)(st(\d?))/g);

for (let match of matches) {
  console.log(match);
}
// ['test1', 'e', 'st1', '1', ...]
// ['test2', 'e', 'st2', '2', ...]
// ['test3', 'e', 'st3', '3', ...]

与 match 的区别

js 复制代码
// match 带 g 标志时,只返回匹配的字符串
'test1test2'.match(/t(e)st(\d)/g);
// ['test1', 'test2']

// matchAll 返回完整的匹配信息(包括捕获组)
[...'test1test2'.matchAll(/t(e)st(\d)/g)];
// [
//   ['test1', 'e', '1', ...],
//   ['test2', 'e', '2', ...]
// ]

注意

  • matchAll 要求正则必须有 g 标志
  • 返回的是迭代器,不是数组(可用 ...Array.from() 转换)

8. for...in 标准化枚举顺序

ES11 进一步明确了 for...in 遍历对象字符串键时的顺序:

  1. 整数索引形式的键(按数值升序)
  2. 其他字符串键(按创建顺序)

注意: for...in 不会遍历 Symbol

js 复制代码
let obj = {};
obj[2] = 'b';
obj[0] = 'a';
obj[1] = 'c';
obj['name'] = '张三';
obj['age'] = 18;
obj[Symbol('id')] = 1;

for (let key in obj) {
  console.log(key);
}
// 输出顺序:'0', '1', '2', 'name', 'age'
// Symbol('id') 不会被 for...in 遍历到

总结

特性 说明 重要性
?. 可选链 安全访问深层属性 ⭐⭐⭐⭐⭐
?? 空值合并 更精确的默认值设置 ⭐⭐⭐⭐⭐
BigInt 任意精度大整数 ⭐⭐⭐⭐
Promise.allSettled() 获取所有 Promise 结果 ⭐⭐⭐⭐
import() 动态导入 按需加载模块 ⭐⭐⭐⭐
globalThis 统一全局对象 ⭐⭐⭐
String.matchAll() 获取所有正则匹配 ⭐⭐⭐
for...in 顺序 统一属性枚举顺序 ⭐⭐
相关推荐
__sgf__2 小时前
ES8(ES2017)新特性
前端·javascript
__sgf__2 小时前
ES9(ES2018)新特性
前端·javascript
送鱼的老默2 小时前
学习笔记--vue3 watchEffect监听的各种姿势用法和总结
前端·vue.js
你挚爱的强哥2 小时前
解决:动态文本和背景色一致导致文字看不清楚,用js获取背景图片主色调,并获取对比度最大的hex色值给文字
前端·javascript·github
英俊潇洒美少年2 小时前
js 同步异步,宏任务微任务的关系
开发语言·javascript·ecmascript
用户69371750013842 小时前
Android 手机终于能当电脑用了
android·前端
wooyoo2 小时前
花了一周 vibe 了一个 OpenClaw 的 Agent 市场,聊聊过程中踩的坑
前端·后端·agent
angerdream2 小时前
最新版vue3+TypeScript开发入门到实战教程之路由详解
前端·javascript·vue.js
送鱼的老默2 小时前
学习笔记--vue3 watch监听的各种姿势用法和总结
前端·vue.js