必须先 await 获取异步结果,再对其深度冻结,而非冻结 await 表达式;object.freeze 仅同步作用于已解析对象,需递归冻结嵌套结构或结合不可变库保障深层不可变性。

不能直接用 await 配合 Object.freeze “冻结异步结果”,因为 await 是运行时行为,Object.freeze 是同步操作——你只能在异步值**拿到之后**再冻结它,而不是“冻结一个 await 表达式”。关键在于:先等待、再冻结、再封装为不可变实体。
明确时机:冻结必须发生在 Promise resolve 之后
Object.freeze 只作用于当前对象,对 Promise 本身或 await 表达式无意义。例如以下写法无效:
const frozen = Object.freeze(await fetch('/api').then(r => r.json())); // ❌ 错误理解:freeze 不会阻止后续赋值,但这里 await 已完成,freeze 有效;问题在于没考虑嵌套引用可变
真正风险在于:如果返回的是普通对象(尤其含嵌套对象/数组),仅顶层 freeze 不足以保证深层不可变。需递归冻结或使用不可变数据结构策略。
构造不可篡改状态实体的三步法
-
等待异步获取原始数据:用
await或.then()获取解析后的 plain object/array -
深度冻结或替换为不可变结构:对结果递归调用
Object.freeze,或用structuredClone+ 冻结(适用于简单 JSON 数据),或借助immer/immutable-js构建真正不可变副本 -
封装为只读原型实例:定义一个类或工厂函数,内部执行上述逻辑,并通过
Object.setPrototypeOf或Object.freeze(Object.getPrototypeOf(instance))锁定原型方法(防止覆盖方法)
简易安全实现示例(适合 JSON-like 数据)
以下是一个轻量级工厂函数,返回深度冻结后的只读状态对象:
async function createFrozenState(fetcher) {
const data = await fetcher();
const deepFreeze = (obj) => {
if (obj !== null && typeof obj === 'object') {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] !== null && typeof obj[prop] === 'object') {
deepFreeze(obj[prop]);
}
});
}
return Object.freeze(obj);
};
return deepFreeze(data);
}
<p>// 使用
const userState = await createFrozenState(() => fetch('/user').then(r => r.json()));
// userState 及其所有嵌套属性均不可修改(严格模式下赋值会静默失败或抛错)
</p>
进阶:结合 class 与私有字段强化不可变语义
若需更清晰的 API 和更强约束,可用 ES2022 私有字段 + 构造时冻结:
class FrozenState {
#data;
constructor(asyncInitializer) {
this.#data = Object.freeze(this.#deepFreeze(await asyncInitializer()));
}
#deepFreeze(obj) { /* 同上 */ }
get value() { return this.#data; }
toJSON() { return this.#data; }
}
<p>// 实例化即冻结,外部无法访问 #data,也无法修改 value 返回的对象(已深度冻结)
const state = await new FrozenState(() => Promise.resolve({ id: 1, profile: { name: 'Alice' } }));
</p>
核心不是语法技巧,而是把“异步获取 → 数据规范化 → 深度冻结 → 封装隔离”作为固定流程。冻结本身不解决异步问题,它只是确保一旦状态就位,就再无人能意外修改它。











