实现 JavaScript 只读属性的多种方法

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() 访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值