You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionisOwnPropertyWritable(obj,prop){constdes=Object.getOwnPropertyDescriptor(obj,prop);returndes==null||des.writable||!!des.set;}classA{geta(){return'a';}}constobj=newA();console.log(isOwnPropertyWritable(obj,'a'));// trueconsole.log(obj.a);// aobj.a='b';console.log(obj.a);// a
这是一个咋一听好像很简单,但是实际上却没那么简单,而且是很有趣的问题。
我们先来看一下什么情况下一个对象的属性是可写的。
“属性可写”这个概念并没有严谨的定义,我们这里先来规定一下。
属性可写,是指满足如下条件:
对于任意对象object,该对象的
a
属性可写,是指如下代码成立:JavaScript有几种情况下,对象属性不可写。
👉🏻 第一种情况,如果这个属性是accessor property,并且只有一个getter时,这个属性不可写。
👉🏻 第二种情况,如果这个属性的Descriptor中设置了writable为false,这个属性不可写。
👉🏻 第三种情况,目标对象被Object.freeze,实际上也是将对象上所有属性的writable设为了false:
那么了解了这些情况,我们就可以尝试写一个方法来判断对象属性是否可写了:
上面这个方法可以简单判断一个对象自身的属性是否可写,判断逻辑也不复杂,先通过
Object.getOwnPropertyDescriptor(obj, prop)
方法获取对象自身属性的Descriptor,接下来有三种情况对象的这个属性可写:看似好像解决了这个问题,但是,实际上这个判断有很多问题。
首先,最大的问题是,这个方法只能判断对象自身的属性,如果对象原型和原型链上的属性,实际上getOwnPropertyDescriptor是访问不到的,我们看一个简单例子:
上面的代码,我们预期的
isOwnPropertyWritable(obj, 'a')
应该返回false,但实际上却是返回true,这是因为Object.getOwnPropertyDescriptor
获取不到class中定义的getter,该getter实际上是在obj的原型上。要解决这个问题,我们需要沿原型链递归判断属性:
我们实现一个
isPropertyWritable(obj, prop)
,不仅判断自身,也判断一下它的原型链。这样我们就解决了继承属性的问题。
但是实际上这样实现还是有缺陷,我们其实还少了几个情况。
首先,我们处理原始类型,比如现在下面的代码会有问题:
所以我们要修改一下isOwnPropertyWritable的实现:
然后,其实还有一些case,比如:
我们还需要考虑seal的情况。
👉🏻 Object.seal 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。
所以对这种情况我们也要加以判断:
好了,那最后得到的版本就是这样的:
这样就100%没问题了吗?
也不是,严格来说,我们还是可以trick,比如给对象故意设一个setter:
你可能会说,这种trick太无聊了,但是事实上类似下面的代码还是有可能写出来的:
当然要解决这个问题也不是不可以,还要加上一个判断:
所以,要考虑的情况着实不少,也不知道还有没有没考虑周全的。
有可能还真得换一个思路,从定义入手:
这样就解决了问题,唯一的问题是对属性做了两次赋值操作,不过应该也没有太大的关系。
补充:经过大家讨论,上面这个思路也不行,如果属性的setter中执行一些操作,会有很大的问题,比如我们observe一些对象,用这个方法因为写入了两次,可能会触发两次change事件。。。
所以基本上运行时判断某个属性可写,没有特别好的手段,也许只能使用TypeScript这样的静态类型语言在编译时检查,才是比较好的方案~
好了,关于判断对象属性是否可写的方法,你还有什么问题,欢迎在issue中讨论。
The text was updated successfully, but these errors were encountered: