1. Object.defineProperty()
这是最传统的方式,通过 Object.defineProperty() 可以创建 不可修改的属性。
const obj = {};
Object.defineProperty(obj, 'readOnlyProp', {
value: 'This is read-only',
writable: false, // 不可写
configurable: false, // 不可删除
enumerable: true, // 可枚举
});
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value'; // 赋值无效,严格模式下会报错
console.log(obj.readOnlyProp); // 仍然是 "This is read-only"
- writable: false 让属性不能被修改。
- configurable: false 让属性不能被删除或重新配置!
- enumerable: true 允许该属性在 for...in 或 Object.keys() 中被遍历。
⚠️ 注意:
存在一个漏洞,在设置 configurable: true 的前提下,使用 Object.defineProperty() 再次赋值是可以实现修改对象的!
const obj = {};
Object.defineProperty(obj, 'readOnlyProp', {
value: 'This is read-only',
writable: false, // 不可写
configurable: true,
});
Object.defineProperty(obj, 'readOnlyProp', {
value: 'This is read-only',
writable: true, // 可写
});
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value';
console.log(obj.readOnlyProp); // "New Value"
2. Object.freeze()
Object.freeze() 可以让整个对象变成不可变对象,不能新增、删除或修改属性值,包括其 所有属性。
const obj = Object.freeze({
readOnlyProp: 'This is read-only',
});
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value'; // 赋值无效,严格模式下会报错
console.log(obj.readOnlyProp); // 仍然是 "This is read-only"
⚠️ 注意:
Object.freeze() 只冻结 对象的第一层,如果对象内部有 嵌套对象,需要递归冻结:
function deepFreeze(obj) {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return Object.freeze(obj);
}
const deepObj = deepFreeze({
nested: { key: 'value' },
});
deepObj.nested.key = 'new value'; // 赋值无效
console.log(deepObj.nested.key); // "value"
3. ES6 的 get 访问器
利用 getter 让属性只能被读取,不能修改。
class MyClass {
constructor() {
this._readOnlyProp = 'This is read-only';
}
get readOnlyProp() {
return this._readOnlyProp;
}
}
const obj = new MyClass();
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value'; // 赋值无效,不会改变
console.log(obj.readOnlyProp); // "This is read-only"
get readOnlyProp() 只提供 读取权限,没有 set 方法,因此无法修改 readOnlyProp。
⚠️ 注意:
这种方式不能阻止直接修改 _readOnlyProp,所以 _readOnlyProp 应该被视为 私有变量。
或者和 Object.defineProperty() 结合。
const obj = {};
Object.defineProperty(obj, 'readOnlyProp', {
get() {
return 'This is read-only';
},
});
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value';
console.log(obj.readOnlyProp); // "This is read-only"
4. ES6 Proxy 代理
使用 Proxy 代理对象,可以 拦截属性赋值,从而实现 只读。
const obj = { readOnlyProp: 'This is read-only' };
const readOnlyObj = new Proxy(obj, {
set(target, key, value) {
if (key === 'readOnlyProp') {
console.warn(`${key} is read-only and cannot be modified.`);
return false;
}
target[key] = value;
return true;
},
});
console.log(readOnlyObj.readOnlyProp); // "This is read-only"
readOnlyObj.readOnlyProp = 'New Value'; // 控制台警告,并且修改无效
console.log(readOnlyObj.readOnlyProp); // 仍然是 "This is read-only"
Proxy 允许我们拦截 set 操作,并防止特定属性的修改。
5. TypeScript 只读属性
TypeScript 提供 readonly 关键字,可以让属性在 编译阶段 变为只读。
class MyClass {
readonly readOnlyProp: string = 'This is read-only';
}
const obj = new MyClass();
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value'; // ❌ TypeScript 编译报错
readonly 关键字 只能限制编译时的赋值,运行时仍然可以绕过:
(obj as any).readOnlyProp = 'New Value'; // 运行时可以修改
如果需要 运行时也无法修改,可以结合 Object.freeze():
const obj = Object.freeze(new MyClass());
6. Symbol 作为私有属性
使用 Symbol 作为私有属性,使其 无法被外部直接访问或修改。
const _readOnlyProp = Symbol('readOnlyProp');
class MyClass {
constructor() {
this[_readOnlyProp] = 'This is read-only';
}
get readOnlyProp() {
return this[_readOnlyProp];
}
}
const obj = new MyClass();
console.log(obj.readOnlyProp); // "This is read-only"
obj.readOnlyProp = 'New Value'; // 赋值无效
console.log(obj.readOnlyProp); // "This is read-only"
Symbol 使 _readOnlyProp 无法被外部直接访问,只能通过 get 方法读取。
7. 组合多种方式,增强安全性
如果想要实现 最严格的只读属性,可以结合:
- Object.defineProperty()(确保不可写)
- Proxy(拦截修改)
- Object.freeze()(冻结整个对象)
const obj = {};
Object.defineProperty(obj, 'readOnlyProp', {
value: 'This is read-only',
writable: false,
configurable: false,
enumerable: true,
});
const readOnlyProxy = new Proxy(obj, {
set() {
console.warn('readOnlyProp is read-only and cannot be modified.');
return false;
},
});
console.log(readOnlyProxy.readOnlyProp); // "This is read-only"
readOnlyProxy.readOnlyProp = 'New Value'; // 控制台警告,并且修改无效
console.log(readOnlyProxy.readOnlyProp); // 仍然是 "This is read-only"
总结
方法 | 适用场景 | 是否可被绕过 |
---|---|---|
Object.defineProperty() | 单个属性 | ❌ |
Object.freeze() | 整个对象 | 🔄 仅冻结第一层 |
getter (get) | 类属性 | 🔄 仍可直接修改私有变量 |
Proxy | 运行时拦截修改 | ❌ |
TypeScript readonly | 编译阶段限制 | 🔄 运行时可绕过 |
Symbol 作为私有属性 | 避免外部直接访问 | 🔄 仍可通过 Object.getOwnPropertySymbols() 访问 |