ES9(ES2018)新特性

发布时间:2018年6月 ES9 主要完善了异步迭代、对象扩展、正则表达式等功能。


1. 异步迭代器(Async Iteration)

for await...of

用于遍历异步可迭代对象(异步生成器、异步的流等):

js 复制代码
for await (const line of readLines(filePath)) {
  console.log(line);
}

异步生成器

js 复制代码
async function* asyncGenerator() {
  let i = 0;
  while (i < 3) {
    await new Promise(res => setTimeout(res, 1000));
    yield i++;
  }
}

(async function() {
  for await (let val of asyncGenerator()) {
    console.log(val);  // 每秒输出 0, 1, 2
  }
})();

实际应用:分批获取数据

js 复制代码
async function* fetchPages(urls) {
  for (let url of urls) {
    let res = await fetch(url);
    yield await res.json();
  }
}

(async function() {
  let pages = fetchPages(['/api/page1', '/api/page2', '/api/page3']);
  for await (let page of pages) {
    console.log(page);
  }
})();

与同步迭代器的区别

js 复制代码
// 同步迭代器
obj[Symbol.iterator]    // 返回 { next() => { value, done } }

// 异步迭代器
obj[Symbol.asyncIterator]  // 返回 { next() => Promise({ value, done }) }

2. 对象展开运算符(Object Spread)

ES6 引入了数组展开运算符,ES9 将其扩展到对象:

展开合并对象

js 复制代码
let defaults = { theme: 'light', lang: 'zh' };
let userPrefs = { theme: 'dark', fontSize: 14 };

// 合并对象(后面的覆盖前面的)
let settings = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'zh', fontSize: 14 }

克隆对象(浅拷贝)

js 复制代码
let original = { a: 1, b: { c: 2 } };
let clone = { ...original };
clone.a = 10;      // 不影响 original
clone.b.c = 20;    // 影响 original(浅拷贝)

覆盖部分属性

js 复制代码
let user = { name: '张三', age: 18, city: '北京' };
let updatedUser = { ...user, age: 19 };
// { name: '张三', age: 19, city: '北京' }

添加新属性

js 复制代码
let user = { name: '张三' };
let withId = { ...user, id: 1 };
// { name: '张三', id: 1 }

注意事项

  • 展开运算符只展开对象自身的可枚举属性
  • 原型链上的属性不会被展开
  • 值为 undefined 的属性仍会被包含
js 复制代码
let obj = { a: undefined, b: null, c: 1 };
let copy = { ...obj };  // { a: undefined, b: null, c: 1 }

3. 对象剩余运算符(Object Rest)

解构对象时收集剩余属性:

基本用法

js 复制代码
let { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a);    // 1
console.log(b);    // 2
console.log(rest); // { c: 3, d: 4 }

函数参数中使用

js 复制代码
function updateUser({ id, ...changes }) {
  // id 单独取出,其余作为修改项
  console.log(`更新用户 ${id},修改内容:`, changes);
}

updateUser({ id: 1, name: '李四', age: 20 });
// 更新用户 1,修改内容: { name: '李四', age: 20 }

剔除某些属性

js 复制代码
let { password, ...safeUser } = { name: '张三', age: 18, password: '123' };
console.log(safeUser);  // { name: '张三', age: 18 }

嵌套解构

js 复制代码
let { a: { x, ...restA }, ...restObj } = { a: { x: 1, y: 2 }, b: 3 };
console.log(x);      // 1
console.log(restA);  // { y: 2 }
console.log(restObj); // { b: 3 }

注意

  • 剩余属性必须在最后
  • 剩余运算符得到的始终是普通对象
  • nullundefined 不能用展开/剩余运算符

4. Promise.prototype.finally()

无论 Promise 成功还是失败,都会执行的回调:

基本用法

js 复制代码
fetchData()
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => {
    console.log('请求结束,隐藏loading');
    hideLoading();
  });

特点

  • finally 不接收参数,不知道 Promise 是成功还是失败
  • 返回的 Promise 会继承前面 Promise 的结果
js 复制代码
Promise.resolve('ok')
  .finally(() => {
    console.log('清理资源');
    // 没有返回值或返回普通值,不影响最终结果
  })
  .then(res => console.log(res));  // 'ok'

对比 try...catch...finally

js 复制代码
// Promise 方式
fetchData()
  .then(data => processData(data))
  .catch(err => handleError(err))
  .finally(() => cleanup());

// 等同于 try...catch...finally 的效果
async function handle() {
  try {
    let data = await fetchData();
    processData(data);
  } catch (err) {
    handleError(err);
  } finally {
    cleanup();
  }
}

实际应用

js 复制代码
// 数据库连接
function queryDB() {
  let conn = connectDB();
  return conn.query('SELECT * FROM users')
    .finally(() => conn.close());  // 确保连接关闭
}

// 文件操作
function readConfig() {
  let file = openFile('config.json');
  return readFile(file)
    .finally(() => file.close());  // 确保文件关闭
}

5. 正则表达式扩展

5.1 命名捕获组(Named Capture Groups)

(?<name>...) 为捕获组命名:

js 复制代码
// 旧写法:通过索引访问
let re = /(\d{4})-(\d{2})-(\d{2})/;
let match = '2023-12-25'.match(re);
console.log(match[1]);  // '2023'(年)
console.log(match[2]);  // '12'(月)
console.log(match[3]);  // '25'(日)

// 新写法:通过名称访问
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let match = '2023-12-25'.match(re);
console.log(match.groups.year);   // '2023'
console.log(match.groups.month);  // '12'
console.log(match.groups.day);    // '25'

解构使用命名捕获组

js 复制代码
let { groups: { year, month, day } } =
  '2023-12-25'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);

在 replace 中使用

js 复制代码
let re = /(?<firstName>\w+)\s(?<lastName>\w+)/;
let str = 'John Smith'.replace(re, '$<lastName>, $<firstName>');
console.log(str);  // 'Smith, John'

5.2 反向断言(Lookbehind Assertions)

在匹配位置的前面或后面添加条件判断:

js 复制代码
// 正向先行断言(ES5 就有):后面必须跟某个模式
// (?=...)  后面跟...
// (?!...)  后面不跟...

// 反向先行断言(ES9 新增):前面必须跟某个模式
// (?<=...)  前面跟...
// (?<!...)  前面不跟...

// 匹配价格数字(前面有 $ 符号)
let str = '商品价格 $100,运费 $20';
let prices = str.match(/(?<=\$)\d+/g);
console.log(prices);  // ['100', '20']

// 匹配不以 $ 开头的数字
let nums = 'a123 $456 c789'.match(/(?<!\$)\d+/g);
console.log(nums);  // ['123', '789']

5.3 正则表达式 dotAll 模式

dotAll 标志 s,让 . 匹配包括换行符在内的所有字符:

js 复制代码
// 默认情况下,. 不匹配换行符
/hello.world/.test('hello\nworld');   // false

// dotAll 模式下,. 匹配换行符
/hello.world/s.test('hello\nworld');  // true

5.4 正则表达式 Unicode 转义

在正则中可以使用 \p{...} 匹配 Unicode 字符类别:

js 复制代码
// 匹配任何 Unicode 字母(包括中文等)
/\p{Letter}/u.test('你');    // true
/\p{Letter}/u.test('A');     // true
/\p{Letter}/u.test('1');     // false

// 匹配 Unicode 空白
/\p{White_Space}/u.test('\n');  // true

// 否定匹配
/\P{Letter}/u.test('1');     // true,非字母

总结

特性 说明 重要性
for await...of 异步迭代器 ⭐⭐⭐⭐
对象展开运算符 ... 对象合并、克隆 ⭐⭐⭐⭐
对象剩余运算符 ...rest 解构时收集剩余属性 ⭐⭐⭐⭐
Promise.finally() 无论成败都执行 ⭐⭐⭐⭐
命名捕获组 正则捕获组可命名 ⭐⭐⭐
反向断言 正则前面匹配条件 ⭐⭐⭐
dotAll 模式 . 匹配换行符 ⭐⭐
Unicode 转义 正则匹配 Unicode 类别 ⭐⭐
相关推荐
送鱼的老默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
猪八宅百炼成仙2 小时前
解决 el-date-picker type:daterange 在 layout 布局中的宽度问题
前端·element
小贺要学前端3 小时前
ES6 还没用明白,JavaScript 已经快到 ES2026 了
前端·javascript·es6