Object.defineProperty

46 阅读3分钟

1. 语法

Object.defineProperty(obj, prop, descriptor)
  • obj​:要定义或修改属性的目标对象。
  • prop​:要定义或修改的属性名称(String 或 Symbol)。
  • descriptor​:属性描述符对象,用于控制属性的行为。
  • Object.defineProperty()是 JavaScript 中一个非常强大的工具,它允许你精确地控制对象属性的行为。下面我将详细解释其用法,并通过丰富的示例帮助你透彻理解。

属性描述符分为两种主要类型,它们决定了属性的核心工作方式:

一个属性描述符​​只能是其中一种​​,不能同时包含 value/writable和 get/set

描述符类型核心作用可选键
数据描述符定义包含值的属性,并控制该值是否可被修改valuewritableenumerableconfigurable
访问器描述符通过 getter/setter 函数来控制属性的读取和赋值行为getsetenumerable, (configurable可配置性)

2. 缺陷

Object.defineProperty在实现精细属性控制时,存在一些天然的拦截盲区。下面我们通过具体示例来探讨这些问题。

🔍 对象新增/删除属性的拦截问题

Object.defineProperty只能对对象上​​已经存在的属性​​设置 getter/setter 进行拦截。对于初始化后动态​​新增​​或​​删除​​的属性,它无法自动感知。

const person = { name: '小明' };

// 仅为初始属性 'name' 设置拦截
Object.defineProperty(person, 'name', {
  get() {
    console.log('读取 name 属性');
    return this._name;
  },
  set(newVal) {
    console.log('设置 name 属性为:', newVal);
    this._name = newVal;
  }
});

// 测试初始属性
person.name = '小红'; // 控制台输出: "设置 name 属性为: 小红"
console.log(person.name); // 控制台输出: "读取 name 属性", 然后输出: "小红"

// 问题1: 新增属性无法被拦截
person.age = 18; // 控制台没有任何输出,新增属性未被拦截
console.log(person.age); // 输出: 18,读取新增属性时也无任何输出

// 问题2: 删除属性无法被拦截
delete person.name; // 控制台没有任何输出,属性删除操作未被拦截
console.log(person.name); // 输出: undefined

​原因分析​​:Object.defineProperty的作用范围仅限于在调用它时指定的属性。当新的属性被添加到对象上时,如果没有再次调用 Object.defineProperty来显式定义该属性的描述符,引擎就不会为其建立拦截层。删除操作亦是如此。

📊 数组索引与方法的拦截局限

对于数组,Object.defineProperty的局限性更加明显,主要体现在通过索引设置值以及操作数组长度上。

const arr = [1, 2, 3];

// 尝试为数组的索引 0 设置拦截
Object.defineProperty(arr, '0', {
  get() {
    console.log('读取索引 0');
    return this._0;
  },
  set(newVal) {
    console.log('设置索引 0 为:', newVal);
    this._0 = newVal;
  }
});

// 测试1: 直接通过索引设置已存在的元素
arr[0] = 99; // 控制台输出: "设置索引 0 为: 99" (本次有效)

// 测试2: 通过数组方法(如 push)添加新元素
arr.push(4); // 控制台没有任何输出
// push 操作相当于执行了 `arr[3] = 4`,但新索引 3 未被拦截。

// 测试3: 直接通过索引设置新元素
arr[3] = 100; // 控制台没有任何输出,新增的索引 3 未被拦截

// 测试4: 直接修改数组长度
arr.length = 0; // 控制台没有任何输出,无法拦截对 length 属性的修改
console.log(arr); // 输出: [],数组已被清空,但拦截器未触发

Vue 2 基于 Object.defineProperty的实现方式,它也带来了一些固有的限制:

  • ​对象新增/删除属性​​:由于 Object.defineProperty只能劫持初始化时存在的属性,因此直接给对象设置新属性 (this.obj.newProp = value) 或删除属性 (delete this.obj.prop) 是无法被侦测到的。为了解决这个问题,Vue 2 提供了 Vue.set(或 this.$set) 和 Vue.delete 方法 。

  • ​数组的局限性​​:Object.defineProperty无法有效侦测通过数组索引直接设置值 (this.items[index] = newValue) 或直接修改数组长度 (this.items.length = 0)。Vue 2 的选择是​​重写数组的 7 个变更方法​​(如 pushpopsplice等),让这些方法在执行变更操作的同时,能够手动触发更新通知