『EcmaScript篇』之你真的了解对象属性吗?
文章介绍
今天我们来聊一聊大家经常使用到的数据类型Object,平时大家更多使用对象来存储各种键值对,但大家真的了解Object的属性特性吗?如是否可枚举、是否可配置、是否可写等特性,不知大家是否有去了解过呢?本文便着重介绍Object的属性描述符。
一、从getter、setter入手
如果有学习过Java这一类面向对象的代码,就会对getter和setter非常的熟悉,对处于私有访问权限的对象属性的获取和修改都是通过对应的共有getter、setter方法。
而在JavaScript这门动态语言中,提供了非常方便的点语法obj.name
以及键值获取obj['name']
的方法来获取、修改对象的属性值。因此可能比较少的同学会去了解对象赋值中其实也存在着getter/setter,简单使用:
可以看到我们通过obj.foo得到了get伪函数返回的结果123。这就是get函数的基础使用,当然我们可以在get
函数中做对应的数据处理,如果有学习过vue响应式的同学应该知道依赖收集
便是在get中完成的,只不过是使用Object.defineProperty
的方式定义get,这个我们之后会讲到。
通过MDN对Getter的描述我们可以了解到:1. getter和setter最好成对出现、2. getter不要与同名属性同时出现在一个对象中。这里我们还需要了解到,其实我们使用点语法、键值对的方式给对象属性进行赋值也是调用了默认的Getter、Setter。那么如果我们只使用了get而不使用set,是否能使用默认的set覆盖get函数呢?
实践出真知,我们发现当我们使用get
或set
来定义对象属性时,就会直接将默认的getter与setter覆盖,其实从前文代码中不难想到,getter和setter到底要返回或修改什么值呢?是的,getter与setter如果单独使用并没有意义,因为你只能返回一个固定值且这个值无法被修改。因此我们需要在对象所处的词法作用域中设置一个供对象使用的内部值
,当然这种方法不是最佳方案。
上述案例便是一个setter、getter使用的一个较为完整的案例,同时我们是可以在set
中打印出实参对象的类数组的,因此set
本质上也是一个Function
类型,只不过我们无法通过点语法直接获取到这个函数。
补充一点,虽然get、set是伪函数,但实质上还是定义在obj对象上的方法,因此很好的可以判断出函数get、set的this指向为执行该方法的obj对象。如果对this指向不了解的朋友可以查看笔者的另一篇文章:『ECMAScript篇』之this指向。
通过实践我们可以看到get函数中的this指向的是当前对象obj,当然这里大家能看到属性b为[Getter/Setter]是因为当前的执行环境为Nodejs,如果在浏览器上会是一个需要展开的属性。这里再补充一点:平常大家看到的需要展开的属性实际上当时是没有取到值的,当你点击展开时,会调用一次该属性的getter,这样浏览器才能展示get函数返回的值。
二、获取属性描述符的好帮手
别担心,对象构造器给我们提供了可以查询到set、get
的静态方法:Object.getOwnPropertyDescriptor
,通过这个方法我们就可以得到对象属性的描述符信息。包括我们设置给foo
的setter、getter。
那么我们举个例子:
这个函数的使用方法便是:第一个参数为需要查询描述符的对象,第二个参数为需要查询的属性名,这里为什么会是字符串呢?想必对Object有一定了解的同学都知道:Object的键名最终都会被转换为字符串的形式,类似JSON数据的结构。 同时我们也看到了属性b所含有的其他描述符:enumerable、configurable,这些是什么呢?
三、从enumerable到defineProperty
enumerable顾名思义可枚举的,这个描述符代表的意义就是是否可以被枚举。那么是否可枚举又是什么意思呢?首先只有具有可枚举的对象属性可以通过for..in..
循环得到,同时如果使用console.log
是无法打印出枚举型为false
的对象属性。
这里要先铺垫一点:通过点语法、键值对赋值的属性是默认将enumerable、configurable等属性设置为true的:
因此我们才可以对属性进行枚举、赋值以及删除操作。但是通过这类默认赋值的方式设置的描述符在一般情况下是无法被修改的,那我们如何定义一个属性且为其设置相应的描述符呢?对象构造器同样提供了这么一个接口Object.defineProperty
,这个接口想必大家不会陌生,我们可以使用这个API给对象定制的赋值:
从这个例子中我们可以知道两点:1.验证了enumerable为false的属性是无法通过console.log
打印的、2. 使用defineProperty没有定义的描述符默认会设置为false。
知道了enmuerable这么一个特性,那我们就可以利用他给getter、setter提供一个在对象内部的内部值来替代同词法作用域下的内部值:
四、可写配置writable
接着我们来谈谈writable
这个描述符,这个描述符的意义是对象属性是否能够被修改,即关闭了对象属性的可改属性,就无法使用点语法修改该对象属性的属性值。这里要注意的是:Getter/Setter与writable、value不能同时存在,这在MDN上也有明确标明:
接着我们写一个简单的案例来说明一下:
通过上述案例,我们可以看到对obj.unwrittern
进行的修改操作是无效的。这其实是做了一个静默失败
即解析到这行时发现该属性无法被修改就会直接跳过这条代码,但注意了!如果是在严格模式下,这一行代码就会报错:
五、操作权限configurable
configurable这个描述符控制着两个行为:1.对象属性的删除行为、2.修改对象属性的操作符行为。也就是说如果configurable=false
,那么此时便无法使用delete
操作删除对象的属性、无法使用defineProperty
再次修改属性的描述符。其实第二点是可以利用第一点来解释的,因为重新定义一个元素是需要将这个属性先删除再进行添加的,基于第一点我们便无法将删除这个属性,当然 这么做是会报错的:
接着验证第一个特性:
这里博主偷了个懒,将两个特性写到了一个例子中。从上述例子中我们可以观察到:1. 如果configurable为true,我们便可以使用defineProperty设置一个新的属性将其覆盖、2. 如果configurable为false我们就无法使用delete将其删除。
六、提升部分:对象密封
通过前文的学习,想必大家都对属性的描述符有了一定的了解。那么我们可以利用这些描述符做些什么呢?
1. 密封对象之对象不可拓展
那么如果我们想要密封一个对象使之可以被修改、删除,但是不允许其添加新的属性应该怎么做呢?非常合理的对象构造器又给我们提供了一个相应的方法:Object.preventExtensions
:
从上例中的例子我们可以观察到使用Object.preventExtensions
之后对象就无法再继续添加属性,同时查看一个对象是否为不可拓展对象可以使用方法Object.isExtensible
。获取到的的值为true
则为可拓展对象即可以向其继续添加属性,若为false
则为不可拓展。
2. 正式入手密封对象之Object.seal
有了上文的铺垫,我们可以正式的入手对象密封的操作了。首先是比较基础的密封,在这种情况下我们无法给对象添加新的属性,也无法删除对象上的属性,不过我们可以修改已有属性的属性值。我们先来看看MDN上关于Object.seal的描述:
在Object.seal中我们可以看到了前文所提的静默失败
以及严格模式下抛出错误的问题。对象密封的方法与前文的禁止对象拓展方法一致:
通过上述的例子,我们可以观察到无法向被密封的对象中添加新的属性且无法删除已有的属性,但是可以通过对象赋值的方式修改已有属性。 那么此时的obj对象的属性描述符又是如何呢?
我们从结果可以看到,Object.seal
仅仅只是将属性描述符中的configurable
设置为false,同时将对象设置为不可拓展,那么我们可以通过之前学过的Object.defineProperty
自定义一个自己的seal密封函数:
利用之前的例子进行验证:
通过验证,我们发现达成了与Object.seal相同的结果,至此Object.seal完成~
3. 正式入手密封对象之Object.freeze
如果有看过笔者另一篇文章的同学可能对Object.freeze并不陌生。这个方法可以冻结这个对象,其实也就是在Object.seal基础上继续密封,使对象的属性不能被修改且这个对象的原型对象也无法被修改:
关于对属性值增删改查我就不再举例子说明了,且Object.freeze的重写与Object.seal相差只不过是在writable设置为false。这里便不再赘述,大家可以自行测试。
七、总结
以上便是本文的全部内容~在文章中我们介绍了属性所具有的几个描述符,着重介绍了其中的Getter/Setter的内容。顺着Getter/Setter我们了解到了defineProperty,接着带着大家通过一例例的案例来更深的了解对应描述符所起的作用,随后介绍了对象密封,希望大家能够有所收获。 我是Donp1~下次见啦!
转载自:https://juejin.cn/post/7176278510057553977