defineProperty是什么?用来干啥的?

258 阅读4分钟

虽然说背八股的时候嘎嘎猛,但是一想下来还没仔细研究过definProperty和Proxy这两个方法,于是便有了这个篇文章。

虽然研究了,但好像也不是很仔细...

废话不多说!

Object.defineProperty

用于在一个对象上定义一个新属性,或者修改一个已存在的属性,并返回该对象。

主要作用是为对象的属性提供更精细的控制,例如可写,可枚举,可配置等。

语法

Object.defineProperty(obj, prop, descriptor)

参数说明

  1. obj
    目标对象,即需要定义或修改属性的对象。
  2. prop
    要定义或修改的属性名称(字符串或 Symbol)。
  3. descriptor
    属性描述符,是一个对象,用于定义或修改属性的行为。它包含以下可选字段:
  • 数据描述符

    • value: 属性的值,默认为 undefined
    • writable: 是否可以修改属性的值,默认为 false
    • enumerable: 是否可以通过 for...inObject.keys() 枚举,默认为 false
    • configurable: 是否可以删除该属性或修改其描述符,默认为 false
  • 存取描述符 ):

    • get: 一个函数,用作属性的 getter,默认为 undefined
    • set: 一个函数,用作属性的 setter,默认为 undefined

示例

1. 定义只读属性

const obj={}
 Object.defineProperty(obj,'name',{
    value:'张三',
    writable:false,//是否可写
    enumerable:true,//是否可枚举
    configurable:true//是否可以删除该属性或修改其描述符
 })//定义属性

 console.log(obj.name);// 张三
 obj.name='李四'
 console.log(obj.name)// 张三

2. 使用存取描述符(getter 和 setter)

const obj={
    _name:'张三'
}

Object.defineProperty(obj,'name',{
    get(){
        console.log(222);
        return this._name
    },
    set(newName){
        this._name=newName
        console.log(111);

    },
    enumerable:true,
    configurable:true
})

console.log(obj.name);// 222,张三 |触发getter
obj.name='李四'// 111 |触发setter
console.log(obj.name)// 222,李四 |触发getter

3. 创建不可枚举属性

const obj={
    age:12
}

Object.defineProperty(obj,'secret',{
    value:[1,2,3], 
    enumerable:false
})

console.log(obj.secret);// [ 1, 2, 3 ]
console.log(Object.keys(obj));// [ 'age' ] |secret不可被枚举

4. 禁止删除属性

const obj = { 
    name: '张三' 
};

Object.defineProperty(obj, 'name', {
  configurable: false // 禁止删除或重新定义
});

delete obj.name; // 删除无效
console.log(obj.name); // 张三

不足

1. 无法监听数组的变化

Object.defineProperty 除了直接对被监听的属性进行修改,无法直接监听数组的索引变化或长度变化。例如,调用某些数组方法(如 pushpop 等),它不会触发拦截逻辑。

const arr = [1];

let originalValue = arr[0]; // 保存原始值

Object.defineProperty(arr, '0', {
  get() {
    console.log('长度1',arr.length);
    return originalValue; // 返回保存的值
  },
  set(value) {
    console.log(`长度2`,arr.length);
    originalValue = value; // 更新保存的值
  }
});

arr.pop()
arr.push(4)
arr[0]=5// 长度1 1
arr[1]=6

可见pop和push对索引0的位置操作,并没有被监听到,为什么呢?

因为数组方法(如 pushpop 等)直接操作数组的底层存储结构,绕过了属性访问器。 就如同push方法相当于arr[arr.length]=5,这种写法本质上是一个动态赋值操作,js引擎会先计算出arr.length的值,然后将其作为索引直接写入数组底层结构

但若是使用arr[0]=5,这时索引已经被明确指定,js引擎就会检查索引是否被拦截,从而触发set或get

在 Vue.js 2.x 中,为了绕过这个限制,Vue 对数组的常用方法(如 pushpop 等)进行了重写,但这增加了实现复杂度。

2. 性能问题

面对需要对多个属性进行监听的时候,需要给属性逐个定义Object.defineProperty,这会导致性能消耗增大。尤其是需要动态添加或者删除属性时,会频繁调用

// 使用 Object.defineProperty 定义属性
console.time('Object.defineProperty');
const obj1 = {};
for (let i = 0; i < 10000; i++) {
  Object.defineProperty(obj1, `key${i}`, {
    value: i,
    writable: true,
    enumerable: true,
    configurable: true
  });
}
console.timeEnd('Object.defineProperty');
// Object.defineProperty: 10.236ms


// 使用普通赋值
console.time('普通赋值');
const obj2 = {};
for (let i = 0; i < 10000; i++) {
  obj2[`key${i}`] = i;
}
console.timeEnd('普通赋值');
// 普通赋值: 2.938ms

3. 无法拦截新增属性

Object.defineProperty 只能对已存在的属性进行定义或修改,无法自动拦截新增的属性。如果需要监听新增属性,必须手动调用 Object.defineProperty

const obj = {};

Object.defineProperty(obj, 'name', {
  value: '张三',
  writable: true,
  enumerable: true,
  configurable: true
});

obj.age = 25; // 新增属性不会触发拦截逻辑
console.log(obj.age); // 25

4. value和get同时使用会报错

const obj = {};
Object.defineProperty(obj, 'name', {
  value: '张三',
  get() { return '李斯'; } // 抛出错误
});

5. 不支持深度监听

const obj = {
    info: {
      name: '张三'
    }
  };
  
  let originalValue = obj.info; // 保存原始值
  
  Object.defineProperty(obj, 'info', {
    get() {
      console.log('获取 info');
      return originalValue; // 返回保存的值
    },
    set(value) {
      console.log('info 被修改');
      originalValue = value; // 更新保存的值
    }
  });
  
  // 修改顶层属性
  obj.info = { name: '李四' }; // 输出: info 被修改
  console.log(obj.info);      // 输出: 获取 info  { name: '李四' }
  
  // 修改嵌套属性
  obj.info.name = '二狗';  // 不会触发拦截逻辑
  console.log(obj.info);      // 输出: 获取 info  { name: '二狗' }

总结

Object.defineProperty 的缺点主要集中在以下几个方面:

  1. 无法监听数组变化。
  2. 性能较低,尤其是在处理大量属性时。
  3. 无法拦截新增属性或删除属性。
  4. 不支持深度监听。
  5. 功能有限,无法拦截函数调用或其他操作。

所以在ES6出现了Proxy,而Object.defineProperty也逐渐被取代