对象属性描述符当有人跟你提到对象属性描述符时,可能是被逼的。而如果你提到对象属性的get/set方法,你就会秒懂。这里的标准描述和习惯表达有些差异,但都指向同一概念所涉及的事物。对象属性描述符在编程实践中是通过Object对象的defineProperty方法暴露给我们的。因此弄清楚Object.defineProperty是理解对象属性描述符的唯一方法。Object.defineProperty,defineProperty翻译成中文就是定义属性,顾名思义就是为对象定义或修改属性的细节,即通过属性描述符来定义读写属性的细节。使用此方法可以精确添加或修改对象属性。熟悉vue的朋友对defineProperty应该不陌生:Object.defineProperty(obj,prop,descriptor)defineProperty接受3个参数,obj表示要修改或定义属性的对象,prop是要定义或修改的属性名称,描述符属性描述符用于定义属性的特性。描述符是一个对象,对象中有两类属性描述符:数据描述符和访问描述符。数据描述符是一个具有值的属性,它可能是可写的,也可能是不可写的。访问描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不是都。数据描述符和访问描述符都有以下可选键值(特征):configurable:如果为false,任何试图删除目标属性或修改该属性的以下属性(可写、可配置、可枚举)的行为都将失效,默认值为假。enumerable:是否可以枚举。即是否可以for-in遍历。默认值为falsewritable:是否可以修改该值。默认为falsevalue:这个属性的具体值是多少。默认为未定义的访问描述符:get:当访问目标属性时会回调该方法,并将该方法的运行结果返回给用户。默认是undefinedset:这个方法会在target属性赋值的时候回调。默认是undefined描述符可以同时拥有的键:configurableenumerablevaluewritablegetset数据描述符YesYesYesYesYesNoNo访问描述符YesYesNoNoYesYes如果一个描述符没有关键字value,writable,get和set中的任何一个,那么它将被认为是一个数据描述符。如果描述符同时具有(value或writable)和(get或set)关键字,则会引发异常。所以value、writable和get/set不能同时设置。varobj={}obj.a=123Object.defineProperty(obj,"newDataProperty",{value:101,//设置值writable:true,//值可以修改enumerable:true,//可以枚举configurable:true//属性可以删除,属性可以修改})给上面的对象obj添加一个新的属性'newDataProperty',并设置属性的属性。在ES5之前,我们只能设置字面量值或对象属性的引用。浏览器支持Object.defineProperty方法后,就像给了我们一个显微镜,可以在更小的粒度上控制属性的行为和特性。:定义属性的可访问行,读写值的规则等。如果对象中不存在指定的属性,Object.defineProperty()将创建该属性。如果该属性已经存在,Object.defineProperty()将尝试根据描述符中的值和对象的当前配置修改该属性。如果旧描述符将其可配置属性设置为false,则该属性被视为“不可配置”并且不能更改任何属性(除了可写为false的单向更改)。当属性不可配置时,您不能在数据和访问器属性类型之间切换。未在描述符中明确设置的属性使用它们的默认值。下面通过几个例子来演示这些特性的具体表现:configurableletfoo={a:1}deletefoo.aObject.defineProperty(foo,'b',{value:2,//默认值为2configurable:false//不允许删除和修改})deletefoo.b//不能删除foo.b=999//不能修改console.log(foo.b)//2enumerableletfoo={a:1,b:2,c:3}for(letiinfoo){//a,b,c可以枚举console.log(`key:${i},value:${foo[i]}`)}Object.defineProperty(foo,'a',{enumerable:false//设置属性不可枚举})for(letiinfoo){//a不可枚举console.log(`key:${i},value:${foo[i]}`)}writableletfoo={a:1}//修改foo.a的值foo.a=2console.log(foo.a)//2Object.defineProperty(foo,'a',{writable:false//设置值不可修改})//尝试修改foo.a的值foo.a=3//不可修改console.log(foo.a)//2valueletfoo={}Object.defineProperty(foo,'a',{value:1//将属性的值设置为1})console.log(foo.a)//1get/setletfoo={a:1}Object.defineProperty(foo,'b',{get:function(){return`hi,${this.值}`},设置:函数(值){这个.a=value//将输入值保存在同一对象的属性a中this.value=value+1}})console.log(foo.b)//'hi,undefined'foo.b=1console.log(foo.a)//1console.log(foo.b)//hi,2注意:get没有参数,set接受实参作为当前设置的值。在get和set函数内部,可以通过this.value访问值特性,该特性用于获取或设置属性的值。当值取决于内部数据时,经常使用get/set。有必要尽可能同时设置get和set。如果只设置了get,那么我们将无法设置属性值。如果只设置了set,??我们就无法读取这个属性的值。Object.defineProperty只能设置一个属性的描述符。当需要设置多个属性描述符时,可以使用Object.defineProperties:letfoo={}Object.defineProperties(foo,{a:{value:1,configurable:true},b:{get:function(){returnthis.value?`hi,${this.value}`:0},set:function(value){this.value=value+1}}})console.log(foo.a)//1console.log(foo.b)//0foo.b=2console.log(foo.b)//'hi,3'我们可以通过Object.getOwnPropertyDescriptor获取某个属性的特征集:letfoo={a:1}Object.defineProperty(foo,'a',{value:2,//将值设置为2writable:false,//值不能修改configurable:false//设置的属性不能删除,属性不能修改})letfooDescripter=Object.getOwnPropertyDescriptor(foo,'a')console.log(fooDescripter)//获取的属性如下//{//configurable:false,//enumerable:true,//value:2,//writable:false//在这里,需要注意的是,Object.defineProperty创建了一个对象的新属性,并修改了一个已有的属性。创建新属性的默认描述符键值为false或未定义。当修改一个已经存在的属性的描述符时,如果该属性之前没有以原来的方式设置或添加到对象中,那么该属性的可配置、可枚举和可写描述符将默认为true。举个例子来理解具体的区别:letfoo={}Object.defineProperty(foo,'a',{value:2//设置值为2})letfooDescripter=Object.getOwnPropertyDescriptor(foo,'a')console.log(fooDescripter)//获取的特征如下//{//configurable:false,//不可删除修改//enumerable:false,//不可枚举//value:2,//可写:false//该值不可修改//}变量a由Object.defineProperty方法创建,所有属性描述符的默认值为false。我们可以通过最后两个代码示例体会到区别:两个示例中都没有预先设置可枚举属性描述符,而是在不同的情况下取值不同。本系列原则上不会讲API,但是属性描述符可以加深我们对javascript和底层框架的理解。
