【JavaScript】const 定义的对象禁止修改内部属性
在JavaScript中,使用const
声明对象只能确保对象引用不被重新赋值,而对象的内部属性默认仍可修改。如果我们想要禁止修改内部属性可以使用如下方法:
🔒 1. 使用Object.freeze()
冻结对象
作用:使对象不可扩展、不可删除属性,且属性值不可修改(浅冻结)。
const obj = { name: 'Alice', details: { age: 25 } };
Object.freeze(obj);obj.name = 'Bob'; // 修改失败(严格模式报错)
obj.newProp = 'test'; // 添加失败
delete obj.name; // 删除失败
console.log(obj); // 输出原对象 { name: 'Alice', ... }
局限:只能冻结第一层属性,嵌套对象仍可修改(如obj.details.age = 30
生效)。
🌳 2. 深度冻结:递归冻结嵌套对象
通过递归冻结所有层级的属性:
function deepFreeze(obj) {Object.freeze(obj);Object.keys(obj).forEach(key => {if (typeof obj[key] === 'object' && obj[key] !== null) {deepFreeze(obj[key]); // 递归冻结嵌套对象}});
}const user = { profile: { name: 'Alice', settings: { theme: 'dark' } } };
deepFreeze(user);
user.profile.settings.theme = 'light'; // 修改失败
console.log(user.profile.settings.theme); // 仍为 'dark'
适用场景:需完全不可变的配置对象或全局状态。
🛡️ 3. 使用Proxy
拦截修改操作
通过Proxy
自定义set
行为,禁止修改:
const handler = {set(target, key) {throw new Error(`禁止修改属性 ${key}`);// 或静默失败:return true; // 不实际修改}
};const obj = new Proxy({ name: 'Alice' }, handler);
obj.name = 'Bob'; // 抛出错误:禁止修改属性 name
优点:灵活控制修改行为(如仅允许特定条件修改)。
🔐 4. 闭包封装私有变量
通过闭包隐藏数据,仅暴露访问方法:
function createPerson(name) {let _name = name; // 闭包保护变量return {getName: () => _name,setName: newName => { _name = newName } // 可控修改};
}const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
person._name = 'Bob'; // 无效(_name不可直接访问)
适用场景:需要封装私有状态且支持受控修改的场景。
🧩 5. TypeScript的readonly
修饰符
在TypeScript中编译时检查:
interface User {readonly name: string;readonly settings: { readonly theme: string }; // 嵌套需手动标记
}const user: User = { name: 'Alice', settings: { theme: 'dark' } };
user.name = 'Bob'; // 编译时报错
user.settings.theme = 'light'; // 编译通过!需额外冻结嵌套对象
局限:仅编译时检查,运行时仍需Object.freeze
配合。
⚡ 6. 不可变数据结构(ES15+或三方库)
-
ES15 Record/Tuple(提案阶段):
const record = #{ name: 'Alice', age: 30 }; record.name = 'Bob'; // 运行时报错
-
Immutable.js库:
const { Map } = require('immutable'); const data = Map({ name: 'Alice' }); const newData = data.set('name', 'Bob'); // 返回新对象,原对象不变
优点:真正的不可变性,适合复杂状态管理(如Redux)。
💎 方案对比与选型建议
方法 | 防修改级别 | 嵌套支持 | 运行时保护 | 适用场景 |
---|---|---|---|---|
Object.freeze() | 浅冻结 | ❌ | ✅ | 简单配置对象 |
deepFreeze | 深冻结 | ✅ | ✅ | 全局配置/需完全不可变的数据 |
Proxy | 自定义 | ✅ | ✅ | 需动态拦截修改的场景 |
闭包封装 | 完全私有 | ✅ | ✅ | 封装私有状态 |
TypeScript readonly | 编译时检查 | 需手动标记 | ❌ | 大型TS项目 |
Record/Tuple | 完全不可变 | ✅ | ✅ | 未来ES标准(目前提案阶段) |
Immutable.js | 完全不可变 | ✅ | ✅ | 复杂状态管理(如Redux) |
⚠️ 关键注意事项
- 严格模式影响:
Object.freeze
在严格模式下修改属性会抛出TypeError
,非严格模式静默失败。 - 性能考量:频繁深冻结大型对象可能影响性能,可改用不可变库(如
Immer
)优化。 - 框架兼容性:在Vue/React中使用冻结对象时,需注意框架的响应式机制(如Vue无法追踪冻结对象的变化)。
实际项目中,深冻结 + TypeScript类型检查是最常用组合,兼顾开发体验与运行时安全。对性能敏感场景推荐使用
Immutable.js
或ES Record/Tuple
。