Skip to content

如何在 JavaScript 中实现一个只读属性

方法一:Object.defineProperty()

使用属性描述符 (Property Descriptors) 来精确控制属性的行为。这是最根本的实现方式。

方式 1: 使用数据描述符 writable: false

通过将属性的 writable 特性设置为 false,来禁止对属性值的修改。

  • 核心代码:

    javascript
    const obj = {};
    
    Object.defineProperty(obj, 'name', {
      value: 'Kevin',
      writable: false,
      // configurable 默认为 false
      // enumerable 默认为 false
    });
    
    console.log(obj.name); // 输出: "Kevin"
    
    // 尝试修改属性值
    obj.name = 'Eric'; // 在非严格模式下静默失败,严格模式下会抛出 TypeError
    
    console.log(obj.name); // 依然输出: "Kevin"
  • 重点:

    • 属性描述符中的 configurable 默认值为 false
    • configurable: false 时,该属性的描述符本身就无法被再次配置(例如,无法将 writablefalse 改回 true),属性也无法被删除。这从根本上保证了其只读的稳定性。

方式 2: 使用存取描述符 (只提供 get 方法)

通过为属性定义一个 getter 但不定义 setter,使得该属性在尝试被写入时,因为没有对应的 setter 操作而写入失败。

  • 核心代码:

    javascript
    const obj = {};
    let internalValue = 'Kevin';
    
    Object.defineProperty(obj, 'name', {
      get() {
        return internalValue;
      },
      // 没有定义 set 方法
      // configurable 默认为 false
    });
    
    console.log(obj.name); // 输出: "Kevin"
    
    // 尝试修改属性值
    obj.name = 'Eric'; // 静默失败
    
    console.log(obj.name); // 依然输出: "Kevin"
  • 重点:

    • 同样因为 configurable 默认为 false,后续无法再通过 defineProperty 为该属性添加 setter 或将其改回数据属性。

关联方式: ES6 Getter 语法糖

在对象字面量中直接使用 get 语法,其底层实现原理与 Object.defineProperty 的存取描述符一致,是一种更简洁的写法。

  • 核心代码:

    javascript
    const obj = {
      get name() {
        return 'Kevin';
      }
    };
    
    console.log(obj.name); // 输出: "Kevin"
    obj.name = 'Eric';     // 尝试修改,静默失败
    console.log(obj.name); // 依然输出: "Kevin"

方法二:Proxy

使用 Proxy 创建一个对象的代理。在代理层面拦截对属性的 set 操作,从而阻止修改原始对象。

  • 核心代码:

    javascript
    const target = {
      name: 'Kevin'
    };
    
    const proxy = new Proxy(target, {
      get(target, prop, receiver) {
        return Reflect.get(target, prop, receiver);
      },
      set(target, prop, value, receiver) {
        // 拦截 set 操作,不做任何事或抛出错误
        console.warn(`Warning: Property '${prop}' is read-only.`);
        return true; // 返回 true 表示操作“成功”,但我们并未执行赋值
      }
    });
    
    console.log(proxy.name); // 输出: "Kevin"
    proxy.name = 'Eric';     // 输出警告: Warning: Property 'name' is read-only.
    console.log(proxy.name); // 依然输出: "Kevin"
  • 重点:

    • 这种方式的保护作用于代理对象 (proxy)。必须确保后续所有操作都通过 proxy 对象进行,如果直接操作原始对象 (target),则无法实现只读。

方法三:Object.freeze()

冻结整个对象,使其所有自身属性都变得不可修改、不可添加、不可删除。

  • 核心代码:

    javascript
    const obj = {
      name: 'Kevin',
      age: 30
    };
    
    Object.freeze(obj);
    
    obj.name = 'Eric';     // 静默失败
    obj.age = 31;          // 静默失败
    obj.location = 'USA';  // 尝试添加新属性,静默失败
    
    console.log(obj.name);      // 输出: "Kevin"
    console.log(obj.age);       // 输出: 30
    console.log(obj.location);  // 输出: undefined
  • 重点:

    • 影响范围广: 此方法会影响对象上的所有属性,将整个对象变为只读。如果只想保护单个属性,此方法不适用。
    • 浅冻结: Object.freeze() 是浅操作。如果对象的属性值是另一个对象,那么这个内层对象本身是不被冻结的,其内部属性可以被修改。