第 10 章:ES2026 新特性
Array.fromAsync() (ES2026)
从异步迭代器创建数组,简化异步数据处理:
// 从异步迭代器创建数组
async function* asyncNumbers() {
yield 1;
yield 2;
yield 3;
}
const numbers = await Array.fromAsync(asyncNumbers());
console.log(numbers); // [1, 2, 3]
// 从 Promise 创建数组
const promiseArray = await Array.fromAsync([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]);
console.log(promiseArray); // [1, 2, 3]
// 使用 mapFn 参数
const doubled = await Array.fromAsync(
[Promise.resolve(1), Promise.resolve(2)],
value => value * 2
);
console.log(doubled); // [2, 4]
// 实际应用:从 API 获取数据
async function* fetchPages(ids) {
for (const id of ids) {
const response = await fetch(`/api/pages/${id}`);
yield await response.json();
}
}
const pages = await Array.fromAsync(fetchPages([1, 2, 3]));
Object.groupBy() 和 Map.groupBy() (ES2026)
根据回调函数对数组元素进行分组:
const products = [
{ name: '苹果', category: '水果', price: 5 },
{ name: '香蕉', category: '水果', price: 3 },
{ name: '胡萝卜', category: '蔬菜', price: 2 },
{ name: '西兰花', category: '蔬菜', price: 4 },
{ name: '牛奶', category: '饮品', price: 8 }
];
// Object.groupBy - 返回普通对象
const grouped = Object.groupBy(products, item => item.category);
console.log(grouped);
// {
// 水果:[{name: '苹果', ...}, {name: '香蕉', ...}],
// 蔬菜:[{name: '胡萝卜', ...}, {name: '西兰花', ...}],
// 饮品:[{name: '牛奶', ...}]
// }
// Map.groupBy - 返回 Map (支持任意键类型)
const groupedMap = Map.groupBy(products, item => item.category);
// 根据价格区间分组
const priceGroups = Object.groupBy(products, item => {
if (item.price < 4) return '低价';
if (item.price < 6) return '中价';
return '高价';
});
// { 低价: [...], 中价: [...], 高价: [...] }
// 链式调用
const expensiveByCategory = Object.entries(
Object.groupBy(products, p => p.category)
)
.map(([category, items]) => ({
category,
avgPrice: items.reduce((sum, i) => sum + i.price, 0) / items.length
}));
装饰器 (Decorators) - Stage 3
装饰器提供了一种在声明时修改类和类元素的方式:
// 类装饰器
function logged(target) {
return class extends target {
constructor(...args) {
super(...args);
console.log(`创建 ${target.name} 实例`);
}
};
}
@logged
class Person {
constructor(name) {
this.name = name;
}
}
const p = new Person('张三'); // 输出:创建 Person 实例
// 方法装饰器
function enumerable(value) {
return function(target, context) {
return {
...value,
descriptor: {
...value.descriptor,
enumerable: false
}
};
};
}
class MyClass {
@enumerable
myMethod() {
return 'hello';
}
}
// 属性装饰器
function readonly(target, context) {
return {
get() {
return this.#value;
},
initializer(value) {
this.#value = value;
}
};
}
class Config {
@readonly
apiKey = 'secret-key';
}
Promise.withResolvers() (ES2024)
无需 executor 函数即可创建 Promise:
// 传统方式
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// 新方式
const { promise, resolve, reject } = Promise.withResolvers();
// 实际应用
function loadImage(src) {
const { promise, resolve, reject } = Promise.withResolvers();
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('加载失败'));
img.src = src;
return promise;
}
// 使用
const img = await loadImage('image.jpg');
String.prototype.isWellFormed() 和 toWellFormed() (ES2024)
处理 Unicode 代理对:
// 检查字符串是否是格式良好的 UTF-16
const str1 = 'Hello';
const str2 = '\uD834'; // 孤立的代理项
console.log(str1.isWellFormed()); // true
console.log(str2.isWellFormed()); // false
// 转换为格式良好的字符串
const wellFormed = str2.toWellFormed();
console.log(wellFormed); // "" (替换为 U+FFFD)
Array.prototype.toReversed(), toSorted(), toSpliced() (ES2024)
不可变的数组操作方法:
const numbers = [3, 1, 4, 1, 5];
// toReversed - 返回新数组,不修改原数组
const reversed = numbers.toReversed();
console.log(reversed); // [5, 1, 4, 1, 3]
console.log(numbers); // [3, 1, 4, 1, 5] (未变)
// toSorted - 返回排序后的新数组
const sorted = numbers.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(numbers); // [3, 1, 4, 1, 5] (未变)
// toSpliced - 返回 splice 后的新数组
const spliced = numbers.toSpliced(1, 2, 9, 8);
console.log(spliced); // [3, 9, 8, 1, 5]
console.log(numbers); // [3, 1, 4, 1, 5] (未变)
// with - 返回指定索引替换后的新数组
const withReplaced = numbers.with(2, 99);
console.log(withReplaced); // [3, 1, 99, 1, 5]
console.log(numbers); // [3, 1, 4, 1, 5] (未变)
RegExp v 标志 - Unicode 属性转义 (ES2024)
增强的 Unicode 字符类支持:
// 使用 v 标志的 Unicode 属性转义
const emojiRegex = /[\p{Emoji}]/v;
console.log(emojiRegex.test('😀')); // true
// 集合操作
const letterOrDigit = /[\p{Letter}--[\p{Latin}]]/v;
// 匹配非拉丁字母的字母
// 嵌套字符类
const complex = /[[\p{Emoji}&&\p{Smiley}]--😀]/v;
// 匹配表情符号中的笑脸,但不包括😀
Hashbang 语法 (ES2023)
支持在 JavaScript 文件开头添加 shebang:
#!/usr/bin/env node
// 现在可以在 JS 文件中使用 shebang
console.log('这是一个可执行的 Node.js 脚本');
Object.hasOwn() (ES2022)
更安全的属性检查方法:
const obj = { foo: 1 };
// 传统方式
console.log('foo' in obj); // true
console.log(obj.hasOwnProperty('foo')); // true
console.log(obj.hasOwnProperty('toString')); // true (原型链上的)
// 新方式 - 只检查自身属性
console.log(Object.hasOwn(obj, 'foo')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
// 处理 hasOwnProperty 被覆盖的情况
const obj2 = {
foo: 1,
hasOwnProperty: () => false
};
console.log(obj2.hasOwnProperty('foo')); // false (被覆盖)
console.log(Object.hasOwn(obj2, 'foo')); // true (正确)
Array.prototype.at() (ES2022)
支持负索引的数组访问:
const arr = ['a', 'b', 'c', 'd', 'e'];
// 传统方式获取最后一个元素
console.log(arr[arr.length - 1]); // 'e'
// 新方式
console.log(arr.at(-1)); // 'e'
console.log(arr.at(-2)); // 'd'
console.log(arr.at(0)); // 'a'
// 字符串也支持 at()
const str = 'hello';
console.log(str.at(-1)); // 'o'
Top-level await (ES2022)
在模块顶层直接使用 await:
// 在 ES 模块中可以直接使用 await
const data = await fetch('/api/data').then(r => r.json());
// 动态导入
const module = await import('./module.js');
// 条件加载
const i18n = await import(`./i18n/${navigator.language}.js`);
// 注意:只能在 ES 模块中使用
// HTML 中需要使用 type="module"
// <script type="module" src="app.js"></script>
响应式编程新范式
2026 年流行的响应式模式:
// 使用 Signals (现代响应式原语)
class Signal {
#value;
#listeners = new Set();
constructor(initialValue) {
this.#value = initialValue;
}
get() {
return this.#value;
}
set(newValue) {
this.#value = newValue;
this.#listeners.forEach(fn => fn(newValue));
}
subscribe(fn) {
this.#listeners.add(fn);
return () => this.#listeners.delete(fn);
}
}
// 使用
const count = new Signal(0);
count.subscribe(v => console.log('新值:', v));
count.set(1); // 输出:新值:1
// 计算信号
const double = new Signal(0);
count.subscribe(v => double.set(v * 2));