JavaScript对象只读有四种方案:1. Object.defineProperty设writable: false保护单属性;2. Object.freeze浅冻结整个对象;3. Proxy拦截set实现动态只读;4. Class私有字段+getter封装式只读;需依场景选型。

JavaScript中实现对象属性只读,核心目标是防止意外修改,但不同场景下“只读”的含义和强度差异很大——有的只需约定不改,有的需运行时拦截,有的要彻底冻结结构。选错方案可能带来性能开销、兼容性问题或防护失效。
1. Object.defineProperty + writable: false(基础级只读)
最常用、语义明确的方式,适用于单个属性的简单保护。
- 对已有对象的指定属性设置
writable: false,后续赋值操作静默失败(非严格模式)或抛错(严格模式) - 仅影响该属性本身,不影响嵌套对象;若属性值是对象,其内部仍可被修改
- 支持配置
configurable: false防止属性被 delete 或重新 define
const obj = { name: 'Alice' };
Object.defineProperty(obj, 'name', { writable: false });
obj.name = 'Bob'; // 无效果(非严格模式)或 TypeError(严格模式)
2. Object.freeze()(浅层冻结)
一次性冻结整个对象:所有自有属性变为只读、不可配置、不可枚举,且不能新增/删除属性。
- 仅作用于对象自身属性,不递归冻结嵌套对象(即“浅冻结”)
- 返回原对象(非副本),冻结后无法撤销
- 在现代引擎中优化较好,适合配置对象、常量对象等静态数据
3. Proxy + get/set 拦截(动态可控只读)
通过代理对象拦截所有读写操作,灵活实现条件性只读、日志记录、深层防护等高级逻辑。
立即学习“Java免费学习笔记(深入)”;
- 可在
set拦截器中统一拒绝赋值,或根据 key、值类型、调用上下文做差异化处理 - 天然支持深层嵌套(只要代理链完整),适合构建可调试的只读视图
- 有运行时开销,不宜用于高频访问的性能敏感对象(如动画帧内数据)
const readOnlyObj = new Proxy({ x: 1, y: { z: 2 } }, {
set() { throw new Error('Cannot modify read-only object'); },
defineProperty() { throw new Error('Cannot define property on read-only object'); }
});
4. Class + private fields + getter(封装式只读)
利用 ES2022 私有字段(#field)配合仅暴露 getter,从设计层面约束访问路径。
- 私有字段无法被外部直接访问或修改,getter 可返回计算值或克隆副本,避免外部污染
- 适合需要封装逻辑、校验或惰性计算的场景(如缓存、格式化)
- 不阻止反射(Reflect.ownKeys、getOwnPropertyDescriptors),但符合常规开发约定
class ReadOnlyConfig {
#data = { timeout: 5000 };
get timeout() { return this.#data.timeout; }
// 无 setter,外部无法赋值
}
没有银弹方案:简单常量用 Object.freeze;需细粒度控制选 defineProperty;需深度防护或运行时策略用 Proxy;面向长期维护的业务模型优先考虑 Class 封装。关键是根据可变性边界、协作约定和性能要求做取舍。










