跳转到内容

属性描述符

刀刀

4/7/2025

0 字

0 分钟

概念

创建一个对象 obj ,并为其添加一个属性 x ,值为 10。然后,使用 Object.getOwnPropertyDescriptor() 方法获取 x 属性的描述符。得到了以下的结果:

js
{
  value: 10,
  writable: true,
  enumerable: true,
  configurable: true
}

结果表明,这个对象的 x 属性值是 10,并且是可写的、可枚举的、可配置的。

属性描述符是可以设置的,通过 Object.defineProperty() 方法可以修改属性的特性。例如,将 x 属性值设置为 20,并且只读,不可枚举:

js
Object.defineProperty(obj, 'x', {
  value: 20,
  writable: false, // 只读
  enumerable: false, // 不可枚举
});

设置为不可枚举之后,x 属性将不会出现在 for...in 循环中,也不会出现在 Object.keys() 的结果中,打印 obj 也不会显示 x 属性。

但是没设置 configurablex 属性的描述符还可以修改,此时如果修改 x 的是否可写、是否可遍历属性描述符是允许的。

js
Object.defineProperty(obj, 'x', {
  writable: true, // 可写
  enumerable: true, // 可枚举
});

obj.x = 20
for (let key in obj) {
  console.log(key); // 输出:x
  console.log(obj[key]); // 输出:20
}

如果设置了 configurable: false ,则不能修改 x 属性的描述符,也不能删除 x 属性。

js
Object.defineProperty(obj, 'x', {
  configurable: false, // 不可配置
});

// 此时再修改 x 属性的描述符会报错
Object.defineProperty(obj, 'x', {
  writable: true,
});
// TypeError: Cannot redefine property: x

项目实战

声明一个类,构造器内声明一个数据对象 data ,这个 data 希望是内部修改的,外部只能使用,不能修改它的值。

js
class UIGoods {
  constructor(data) {
    Object.defineProperty(this, 'data', {
      value: data,
      writable: false,
    });
  }
}

const goods = new UIGoods({ name: '商品1', price: 100 });
goods.data = { name: '商品2', price: 200 };

此时虽然外部使用的人无法修改,但是没有任何报错提示。如果是多人大型项目开发,开发者发现修改了 data 属性,但是没有任何报错提示,这会导致混乱。因此有一个报错提示是很有必要的。

访问器

Object.defineProrperty() 方法可以设置访问器属性,访问器属性由一对 getset 函数组成。读取对象的属性,实际上是从 get 函数上获取值,设置对象的属性,实际上是调用 set 函数。

js
Object.defineProperty(obj, 'x', {
  get() {
    console.log('get')
    return 10;
  },
  set(value) {
    console.log(value);
  },
});

obj.x = obj.x + 10
console.log(obj.x);

此时输出的是 get 20 10。首先 obj.x 读取时调用 get 函数,返回 10。然后 obj.x + 10 执行时,触发 set 函数,最后 console.log 时再次触发 get 函数。只不过 set 函数内没有做赋值操作,且 get 函数内写死 return 10,因此最后返回结果还是 10。

解决

结合属性描述符和访问器属性,可以创建一个只读属性,并且在开发者修改属性时,抛出错误。

js
class UIGoods {
  constructor(data) {
    let _data = JSON.parse(JSON.stringify(data));
    // 商品数据
    Object.defineProperty(this, '_data', {
      configurable: false,
      get() {
        return this.data;
      },
      set(value) {
        throw new Error('不可修改 data');
      },
    });

    // 选择了多少商品
    let internalChooseValue = 0
    Object.defineProperty(this, 'choose', {
      configurable: false,
      get() {
        return internalChooseValue;
      },
      set(value) {
        if (typeof value !== 'number') throw new Error('choose 必须是数字');
        let temp = parseInt(value)
        if (temp !== value) throw new Error('choose 必须是整数');
        if (val < 0) throw new Error('choose 必须大于等于 0');
        internalChooseValue = val
      },
    })
  }

  // 计算总价
  get totalPrice() {
    return this.data.price * this.choose
  }
}

let good = new UIGoods({ name: '商品1', price: 100 })
good.data = { name: '商品2', price: 200 } // 报错
good.choose = -10 // 报错
good.choose = 10
console.log(good.totalPrice) // 1000

总结

属性描述符和访问器属性是 JavaScript 对象属性的重要特性,它们允许开发者控制属性的行为和访问方式。通过使用属性描述符可以定义属性的值、可写性、可枚举性和可配置性。而访问器属性则允许通过自定义的 getset 函数来控制属性的读取和设置。