阅读 1516
深入理解Object.defineProperty

深入理解Object.defineProperty

这是我参与更文挑战的第18天,活动详情查看: 更文挑战

没有前言,这次咱们直接单刀直入,直奔主题!

本文思路如上图

首先从名字就可以看出,Object.defineProperty()的作用就是用于定义一个对象的属性,于是很自然的想到三点:

  1. 给哪个对象添加属性呀
  2. 要添加的属性名叫什么呀
  3. 属性名对应的value值是什么呀

正好我们想到这三点对应它的3个必填参数:

let obj = {}
Object.defineProperty(obj, 'name', {
	value: 'Alice'
});
console.log(obj.name)//Alice
复制代码

上述代码中,表示往obj对象中添加一个属性name,值为alice。其中有一点跟想的有出入,就是为什么它是{value:'Alice'},而不是'Alice',先埋个疑问,后面我会讲到!

顺着我们的思路下来,可以很自然的想到Object.defineProperty(obj, prop, descriptor)中的三个传参都是必填。如果参数漏填或者格式不正确,就会报错!例如我第三个参数没写,就会直接报错:Uncaught TypeError: Property description must be an object: undefined

但平时我们往往使用.操作符 来给一个对象添加属性,或者修改已有属性的属性值,例如:

let obj = {}
obj.name = 'alice'
console.log(obj.name)//alice
复制代码

上述代码跟前面的代码实现的功能是一致的,都是给obj对象添加一个属性name,值为alice

到这里你可能会由一个大疑问,既然可以使用.的形式代替Object.defineProperty()的功能,那是不是没有必要使用Object.defineProperty()

你要明白,每个语法都有自己的使用场景的,委员可不是sb!正常情况下,如果仅仅是给对象的一个属性对应的value值,确实没有必要用到Object.defineProperty(),直接使用对象.属性 = xxx的形式就好了。但是如果要给属性配置一些特性,它就能发挥威力了!

于是回到我们上面埋的疑问,Object.defineProperty()的第三个参数其实就是属性对应的一些配置,也被称为属性的描述descriptor,其中例子中写的属性值value就是其中一个配置。那你一定有一个疑问了:除了value配置,还有什么配置呢?这个问题后面会回答你。

值得注意的是:默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的,例如我通过以下两种方式修改defineProperty添加的属性:

  1. 如果通过Object.defineProperty() 重新修改Object.defineProperty() 定义过的属性,会被提示不能重复定义:Uncaught TypeError: Cannot redefine property: xxx

  2. 如果通过对象.属性 = xxx的形式尝试修改用 Object.defineProperty() 新增的属性,虽然没有语法错误,但是当再次访问时,结果还是Object.defineProperty() 之前定义的值

let obj = {}
Object.defineProperty(obj, 'name', {
	value: 'alice'
});
obj.name ='tom'
console.log(obj.name)//alice
复制代码

上述代码中,通过obj.name='tom'再次修改对应的值,访问时还是Object.defineProperty() 之前定义的值alice;

上面这两种情况都是展示了那句话:默认情况下,使用 Object.defineProperty()添加的属性值是不可修改(immutable)的

但这里还有一种情况得区分开,就是先定义了属性的,在通过Object.defineProperty()来修改,如果没有修改相关配置,是可以通过 对象.属性=xxx的形式或者通过Object.defineProperty()再次修改的

let obj = {}
obj.name ='lbj'
console.log(obj.name)//lbj
Object.defineProperty(obj, 'name', {
	value: 'alice'
});
console.log(obj.name)//alice
obj.name ='tom'
console.log(obj.name)//tom
Object.defineProperty(obj, 'name', {
	value: 'aaa'
});
console.log(obj.name)//aaa
复制代码

以上代码中,由于最开始就存在这个属性,所以可以通过obj.name=xxx或者用Object.defineProperty()重复修改

你可能会想,好神奇,怎么这样子的?其实也不用多想,这就是背后的一种机制,记住就好了。

下面回过头来回答之间留下的那个疑问,属性描述中除了value,还有哪些配置项?

这里引用枯燥的一句话:

对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。

事情上,这两种描述符都是对象。它们共享以下可选键值

  1. configurable

表示可配置的,默认值为false;当值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象中删除

  1. enumerable

表示可枚举的,默认为false;当设置为true时,该属性才会在对象枚举时枚举到

除了configurableenumerable可选键值,数据描述符还具有2个可选键值:

  1. value

表示属性对应的值,默认undefined

  1. writable

表示可写的,当writabletrue时,属性值才能被赋值运算符更改成功;默认值为false,所以默认情况下,使用 Object.defineProperty()添加的属性值是不可修改(immutable)的

而存取描述符也不会被亏待,它也还具有以下可选键值:getset方法

  1. get

当获取该属性时,执行get函数,属性值就是get函数的返回值,如下

let obj = {}
Object.defineProperty(obj, 'name', {
	configurable: true,
	enumerable: true,
	get: function () {
		console.log(`你访问了obj.name属性哦`);
		return 'tom';
	}
})
console.log(obj.name)
复制代码

上述代码中,当访问obj.name时,会执行get方法,打印出你访问了obj.name属性哦,同时返回值tom就是该属性的值 如果get没有返回值,默认属性值为undefined get执行时不用传入任何参数,需要注意的是:里头的this指向一般是该对象,但不一定是!

  1. set

当对象的属性修改时调用set函数,例如

let obj = {}
Object.defineProperty(obj, 'name', {
	configurable: true,
	enumerable: true,
	set: function (val) {
		console.log(`设置obj.name的值为:${val}`)
	}
})
obj.name = 'aaa'//
复制代码

上面代码中,设置obj.name的值为aaa,默认会将设置的值aaa作为set函数的第一个参数传递进去,于是打印出:设置obj.name的值为:aaa

于是,我们可以利用Object.defineProperty的特点,做各种各样的事。其中赫赫有名的就是Vue2中的响应式数据系统,就是通过Object.defineProperty实现的!原理很简单,就是通过setget监控数据变化,当数据发生变化时,可通知页面做update等操作!

讲到这里,你应该重新理解了Object.defineProperty吧,没有什么疑问的话,最后我来总结一下:

Object.defineProperty的作用就是用于定义对象的属性的值,它接收三个参数:objpropdescriptor;分别表示添加属性的那个对象、要定义的属性名以及属性配置描述,其中最重要的就是配置描述。分两种:数据描述符和存取描述符

  • 数据描述符可以拥有:

configurable、enumerable、value、writable

  • 存取描述符可以拥有:

configurable、enumerable、get、set

其中configurableenumerablewritable 的默认值都是 falsevaluegetset 的默认值为 undefined

如果一个描述符不具有 valuewritablegetset 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value writablegetset 键,则会报错,即两种各自特有的不能混合

Object.defineProperty()的返回值就是属性要添加在的对象,其实就是第一个参数

END~

一次不写目录的体验:只要表达的思路清晰,应该还是很好阅读的

文章分类
前端
文章标签