发布时间:2022年6月 ES13 新增了顶层 await、类私有字段、数组新方法等特性,是近年来改动较大的版本。
1. 顶层 Await(Top-level Await)
在 ES Module 中,允许在模块顶层直接使用 await,无需包裹在 async 函数中:
基本用法
// config.js
const response = await fetch('/api/config');
export const config = await response.json();
// 直接在顶层 await
const data = await loadData();
console.log(data);
动态依赖加载
// 根据条件加载不同的模块
let strings;
if (isChinese) {
strings = await import('./zh-CN.js');
} else {
strings = await import('./en-US.js');
}
初始化
// 数据库连接初始化
const db = await connectDB();
export { db };
// 资源预加载
const fonts = await Promise.all([
loadFont('/fonts/a.woff2'),
loadFont('/fonts/b.woff2')
]);
注意
- 只能在 ES Module 中使用(
<script type="module">或.mjs文件) - CommonJS 模块不支持
- 顶层 await 会阻塞模块的求值
2. 类私有字段和私有方法
使用 # 前缀定义真正的私有成员:
私有字段
class Person {
#name; // 私有字段(必须先声明)
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
let p = new Person('张三');
console.log(p.getName()); // '张三'
console.log(p.#name); // SyntaxError:私有字段不能在类外访问
console.log('name' in p); // false:私有字段不会出现在 in 操作中
私有方法
class Calculator {
#validate(num) {
if (typeof num !== 'number') throw new Error('必须是数字');
return num;
}
add(a, b) {
this.#validate(a);
this.#validate(b);
return a + b;
}
}
let calc = new Calculator();
calc.add(1, 2); // 3
calc.#validate(1); // SyntaxError:私有方法不能在类外调用
私有静态字段和方法
class MyClass {
static #count = 0;
static getInstance() {
MyClass.#count++;
return new MyClass();
}
static getCount() {
return MyClass.#count;
}
}
console.log(MyClass.getCount()); // 0
MyClass.getInstance();
MyClass.getInstance();
console.log(MyClass.getCount()); // 2
私有字段的 in 检查
class Dog {
#bark = true;
isBarking() {
return #bark in this; // true
}
}
对比旧方式
// 旧方式:约定用 _ 表示私有(但实际可以外部访问)
class OldStyle {
constructor() {
this._private = '可以访问';
}
}
// 新方式:真正的私有(外部完全不可访问)
class NewStyle {
#private = '真正私有';
}
3. 类静态初始化块(Static Initialization Block)
在类中使用 static { } 块执行初始化代码:
基本用法
class MyClass {
static count;
static max;
static {
// 初始化静态字段
MyClass.count = 0;
MyClass.max = 100;
// 可以使用 try...catch
try {
MyClass.config = JSON.parse(fs.readFileSync('config.json'));
} catch {
MyClass.config = { default: true };
}
}
}
多个静态块
class Config {
static db;
static cache;
static {
Config.db = connectDB();
}
static {
Config.cache = new Map();
}
}
访问私有字段
class DB {
static #connection;
static {
DB.#connection = createConnection();
}
static getConnection() {
return DB.#connection;
}
}
4. Array.prototype.at()
通过索引访问数组元素,支持负数索引:
let arr = [1, 2, 3, 4, 5];
arr.at(0); // 1(第一个)
arr.at(2); // 3
arr.at(-1); // 5(最后一个)
arr.at(-2); // 4(倒数第二个)
arr.at(10); // undefined(超出范围)
// 对比旧写法
arr[arr.length - 1]; // 5(旧方式取最后一个)
arr.at(-1); // 5(新方式,更简洁)
也适用于字符串
let str = 'hello';
str.at(0); // 'h'
str.at(-1); // 'o'
也适用于 TypedArray
let typed = new Int8Array([10, 20, 30]);
typed.at(-1); // 30
5. Object.hasOwn()
更安全地检查对象自身是否拥有某个属性:
let obj = { name: '张三' };
// 旧方式
obj.hasOwnProperty('name'); // true
// 问题1:如果对象没有 hasOwnProperty 方法(Object.create(null))会报错
// 问题2:如果属性名就是 hasOwnProperty,会覆盖
// 新方式
Object.hasOwn(obj, 'name'); // true
Object.hasOwn(obj, 'age'); // false
// 安全处理没有原型的对象
let safe = Object.create(null);
safe.key = 'value';
Object.hasOwn(safe, 'key'); // true
// safe.hasOwnProperty('key'); // TypeError!
对比 in 操作符
// in 会检查原型链
'name' in obj; // true
'toString' in obj; // true(继承的)
// Object.hasOwn 只检查自身
Object.hasOwn(obj, 'name'); // true
Object.hasOwn(obj, 'toString'); // false
6. RegExp 的 d 标志(Match Indices)
正则匹配时返回匹配的起始和结束索引:
let re = /test(?<year>\d{4})-(?<month>\d{2})/d;
let match = re.exec('test2023-12');
match.indices; // [[0, 12], [4, 8], [9, 11]]
match.indices.groups; // { year: [4, 8], month: [9, 11] }
match.indices.groups.year; // [4, 8]
match.indices.groups.month; // [9, 11]
// 应用:高亮匹配文本
let text = 'test2023-12';
let [start, end] = match.indices[0];
let highlighted = text.slice(0, start)
+ '<mark>' + text.slice(start, end) + '</mark>'
+ text.slice(end);
// '<mark>test2023-12</mark>'
7. Error 对象的 cause 属性
为错误添加原因链:
try {
try {
JSON.parse('invalid');
} catch (parseErr) {
throw new Error('数据解析失败', { cause: parseErr });
}
} catch (err) {
console.log(err.message); // '数据解析失败'
console.log(err.cause); // 原始的 JSON 解析错误
console.log(err.cause.message); // 'Unexpected token...'
}
自定义错误
class ValidationError extends Error {
constructor(message, field, cause) {
super(message, { cause });
this.field = field;
}
}
try {
throw new ValidationError('年龄必须是数字', 'age', new TypeError('expected number'));
} catch (err) {
console.log(err.field); // 'age'
console.log(err.cause); // TypeError: expected number
}
8. .at() 方法的其他对象支持
at() 方法不仅适用于数组,还适用于:
String
'hello'.at(0); // 'h'
'hello'.at(-1); // 'o'
TypedArray
new Uint8Array([1, 2, 3]).at(-1); // 3
总结
| 特性 | 说明 | 重要性 |
|---|---|---|
| 顶层 Await | 模块顶层直接使用 await | ⭐⭐⭐⭐⭐ |
私有字段 # | 类的真正私有成员 | ⭐⭐⭐⭐⭐ |
私有方法 # | 类的真正私有方法 | ⭐⭐⭐⭐⭐ |
| 静态初始化块 | 类的静态初始化代码 | ⭐⭐⭐⭐ |
Array.at() | 支持负数索引访问 | ⭐⭐⭐⭐ |
Object.hasOwn() | 安全检查自身属性 | ⭐⭐⭐⭐ |
正则 d 标志 | 匹配索引 | ⭐⭐⭐ |
Error.cause | 错误原因链 | ⭐⭐⭐ |