[经] Object.defineProperty()

本文深入解析JavaScript中属性描述对象的概念,包括数据描述符和存取描述符的使用,详解Object.defineProperty方法,演示如何控制对象属性的行为,如可写性、可枚举性和配置性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS提供了一个内部数据结构,用来描述对象的属性,控制对象的行为,这个内部数据结构成为“属性描述对象”,对象中的每个属性都有自己对应的属性描述对象
属性描述对象提供6个元属性。

Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象
语法 : Object.defineProperty ( obj , prop , descriptor )
obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符

例如


let obj = {
	test1 : "peng",
	test2 : "chuan"
}

Object.defineProperty(obj,"test1",{
	value:"fuckyou",//赋值
	writable:false,	//是否可改写
	enumerable:false//是否可遍历
})

属性描述符

对象里目前存在的属性描述符有两种主要形式:1.数据描述符2.存取描述符数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

(1).数据描述符

数据描述中的属性都是可选的,来看一下设置每一个属性的作用。
我们定义一个名为obj1的对象。


let obj1 = {

}

value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。


Object.defineProperty(obj1,"test",{
	value:"fuckyou",//赋值
})
console.log(obj1.test);  //"fuckyou"

writable
属性是否可改写。该属性的writable为true时,value才能被赋值运算符改变。默认为 false。


Object.defineProperty(obj1,"test",{
	 value:"fuckyou",//赋值
	 writable:false,	//是否可改写(false,value值不可改写)
})
console.log(obj1.test); //"fuckyou"
obj1.test = "peng2";
console.log(obj1.test)	//"fuckyou" 未改写


Object.defineProperty(obj1,"test",{
	writable:true,	//是否可改写 (true,value值可改写)
})
console.log(obj1.test); //"fuckyou" 
obj1.test = "peng2";
console.log(obj1.test)  //"peng2" 

enumerable
属性是否可遍历。当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中(使用for…in或Object.keys())。默认为 false。

var obj1 = {
	test1:"值1",
	test2:"值2",
}
Object.defineProperty(obj1,"test1",{
	 enumerable:false//是否可遍历 (false 不可遍历)
})

for(var item in obj1){
	console.log(item)//输出为test2,由此可见test1不可遍历
} 
console.log(Object.keys(obj1))
console.log(Object.getOwnPropertyNames(obj1))

在这里插入图片描述
注意:Object.getOwnPropertyNames( )枚举忽视

configurable
属性描述符是否能被改变, 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。


var obj = {};
Object.defineProperty(obj,"test1",{
	value:"fuckyou",//赋值
	writable:false,	//是否可改写
	enumerable:false,//是否可遍历
	configurable:false//属性描述符是否能被改变,该属性是否能从对象上被删除
})
Object.defineProperty(obj,"test1",{
	value:"fuckyou",//赋值
	writable:true,	//是否可改写
	enumerable:true,
	configurable:false//属性描述符是否能被改变,该属性是否能从对象上被删除
})//报错: Uncaught ReferenceError: test1 is not defined

这个属性起到两个作用:
目标属性是否可以使用delete删除
目标属性是否可以再次设置特性

提示:一旦使用Object.defineProperty给对象添加属性,那么如果不设置属性的特性,那么configurable、enumerable、writable这些值都为默认的false


var obj = {};
//定义的新属性后,这个属性的特性中configurable,enumerable,writable都为默认的值false
//这就导致了neykey这个是不能重写、不能枚举、不能再次设置特性

Object.defineProperty(obj,'newKey',{

});

//设置值
obj.newKey = 'hello';
console.log(obj.newKey);  //undefined

//枚举
for( var attr in obj ){
    console.log(attr);
}

value: 设置属性的值
writable: 值是否可以重写。true | false
enumerable: 目标属性是否可以被枚举。true | false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false

(2).存取器描述

当设置或获取对象的某个属性的值的时候,可以提供getter/setter方法。

getter 是一种获得属性值的方法
setter是一种设置属性值的方法。

当使用存取器描述属性的特性的时候,允许设置以下特性属性:


var obj = {};
Object.defineProperty(obj,"newKey",{
    get:function (){} | undefined,
    set:function (value){} | undefined
    configurable: true | false
    enumerable: true | false
});

当使用了getter或setter方法,不允许使用writable和value这两个属性


var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){			  //当获取值的时候触发的函数
        return initValue;    
    },
    set:function (value){		  //当设置值的时候触发的函数,设置的新值通过参数**value**拿到
        initValue = value;
    }
});
//获取值
console.log( obj.newKey );  //hello

//设置值
obj.newKey = 'change value';

console.log( obj.newKey ); //change value
console.log("initValue=" + initValue) //change value

(附加)

下面的例子展示了如何实现一个自存档对象。 当设置temperature 属性时,archive 数组会获取日志条目。


function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

### Object.defineProperty的缺点 #### 1. 无法监听新增属性 `Object.defineProperty`只能劫持对象已存在的属性,当新增属性时,它无法检测到变化。例如: ```javascript const obj = {}; Object.defineProperty(obj, 'name', { get() { console.log('读取name'); }, set() { console.log('修改name'); } }); obj.name = '张三'; // 能触发setter obj.age = 18; // 无法触发任何反应!新增属性时Object.defineProperty根本不知道 ``` 这意味着如果对象需要动态添加属性并希望这些属性也具有响应性,必须手动重新定义这些属性的描述符。 #### 2. 无法监听删除属性 同样,当从对象中删除属性时,`Object.defineProperty`也无法检测到这一变化: ```javascript delete obj.name; // 同样无法触发反应! ``` 这导致了在处理对象属性的增删时,开发者需要额外的工作来确保状态的一致性和响应性。 #### 3. 对数组的支持有限 `Object.defineProperty`对数组的支持有限,特别是对于数组的方法调用(如`push`, `pop`, `shift`, `unshift`, `splice`, `sort`, `reverse`)不会触发属性的`set`方法。为了克服这个问题,Vue 2.x通过重写数组的方法来实现对数组变化的监听。 #### 4. 需要递归遍历对象 为了使对象的所有嵌套属性都具有响应性,需要递归地对每个属性应用`Object.defineProperty`。这不仅增加了代码的复杂度,而且对于大型对象来说,这可能会导致性能问题。 ```javascript function Observer(obj) { let keys = Object.keys(obj); for (let i of keys) { if (typeof obj[i] == 'object' && obj[i] !== null) { Observer(obj[i]); } Object.defineProperty(obj, i, { get() { console.log(`数据被获取了${obj[i]}...`); return obj[i]; }, set(newValue) { console.log(`数据变化了,obj.${i}:${newValue}...`); obj[i] = newValue; }, enumerable: true, configurable: true }); } } ``` #### 5. 不支持对整个对象的操作 `Object.defineProperty`只能拦截对象的属性访问,对于对象的整体操作(如对整个对象的赋值或属性遍历)并不会被拦截。这限制了其在某些场景下的灵活性和实用性。 ### 相关问题 1. 如何在JavaScript中使用Proxy来解决Object.defineProperty的局限性? 2. Vue 3.0中使用Proxy替代Object.defineProperty的具体实现方式是什么? 3. 在JavaScript中,如何通过自定义方法来实现对数组变化的监听? 4. 什么是Vue.set和Vue.delete,它们是如何帮助解决Object.defineProperty的局限性的? 5. 如何评估Object.defineProperty和Proxy在JavaScript中的性能差异?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值