深入理解Object.defineProperty(),搞懂它的“属性描述符”

642 阅读4分钟

Object.defineProperty()这个函数很有意思,为了充分理解这个函数,特总结如下。欢迎批评指正。
其中引用了MDN,但做出了详细的解读。文章代码可以自行复制后测试一下,再配合本文味道更佳!

对象的定义和赋值

我们常对对象进行定义和赋值,如下

    var obj={}
    obj.name = '此时此地GS'
    obj['gender'] = 'male'
    console.log(obj.name);
    // => 此时此地GS
    console.log(obj.gender);
    // => male

当需要对一个对象的属性进行精细控制时,就需要用到Object.defineProperty()。Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty()的语法

其语法如下:

Object.defineProperty(obj, prop, descriptor)

其中:
obj是要定义的对象
prop是要定义或修改的属性的名称或Symbol

ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而`Object.defineProperty` 是定义key为Symbol的属性的方法之一。

descriptor是要定义或修改的属性描述符。

descriptor属性描述符的具体使用

如何精准控制prop属性呢?就是通过descriptor这个属性描述符实现。descriptor属性描述符是一个对象,里面有6个键值对。如果描述符中的某些键值对被省略,会使用以下默认值:

1.jpg

按照属性之间的关联程度把这些属性列举如下:

一、value

该属性对应的值,可以是任何有效的 JavaScript 值(数值,对象,函数等)。
这个值定义的时候,相当于常用的obj.name = '此时此地GS'方法,效果是一样的。

    var obj={}
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
    })
    console.log(obj.name);
    // => 此时此地GS

二、configurable

当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
当 configurable 键值为 false 时,再次更改 descriptor 属性描述符的属性内容是会报错的,包括get/set/enumerable/writable(对value的影响在第三点里详述),甚至他自己 configurable 也无法再重新定义 (我狠起来连自己都打......);

    var obj={}
    Object.defineProperty(obj,'name',{
      configurable:false,
      // writable:true,
      value:'此时此地GS',
    })
    Object.defineProperty(obj,'name',{
      configurable:true,
      // writable:false,
      // value:'zs',
    })
    console.log(obj.name);
    // => Cannot redefine property: name

这里也有一个例外,当configurable属性为false时,支持writable 属性单向从 true 变为 false,且不会报错

    var obj={}
    Object.defineProperty(obj,'name',{
      configurable:false,
      writable:true,
      value:'此时此地GS',
    })
    Object.defineProperty(obj,'name',{
      writable:false,
      value:'zs',
    })
    console.log(obj.name);
    // => zs

三、writable

1、value值能否改变,受writable属性控制,当writable属性为true时,不管configurable为true还是false,在Object.defineProperty()内外都可以修改value值

    var obj={}
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
      // configurable:true,
      configurable:false,
      writable:true,
      // writable:false
    })
    // Object.defineProperty(obj,'name',{
    //   value:'ls',
    // })

    obj.name = 'zs'
    console.log(obj.name);
    // => zs

2、当writable属性为false时,configurable属性为true时,可以通过Object.defineProperty()重新为value赋值
因为value键值对也是属于descriptor这个属性描述符,因此也会受到configurable属性影响。而通过obj.name = 'zs'这种直接赋值方式无法改变value值。

    var obj={}
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
      configurable:true,
      // configurable:false,
      // writable:true,
      writable:false
    })
    Object.defineProperty(obj,'name',{
      value:'ls',
    })

    // obj.name = 'zs'
    console.log(obj.name);
    // => ls

3、当writable属性为false时,configurable属性为false时,在Object.defineProperty()内外都无法重新更改value值

    var obj={}
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
      // configurable:true,
      configurable:false,
      // writable:true,
      writable:false
    })
    Object.defineProperty(obj,'name',{
      value:'ls',
    })

    // obj.name = 'zs'
    console.log(obj.name);
    // => Cannot redefine property: name

四、enumerable

当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
如果 enumerable 键值为 false 时,使用(for...in 或 Object.keys 方法)枚举对象属性时,会无法枚举出来。

    var obj={gender:'male'}
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
      enumerable:false
    })
    console.log(obj);
    // => {gender: "male", name: "此时此地GS"}

    for(let key in obj ) { console.log( key )}
    // => gender
    console.log(Object.keys(obj));
    // => ["gender"]

五、getset

这俩很像,所以放在一起来说,但这两个是可以单独存在的
get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
用法如下:

    var obj={}
    var temp
    Object.defineProperty(obj,'name',{
      get:function(){
        return temp
      },
      set:function(val){
        temp = val
      }
    })
    obj.name = '此时此地GS'
    console.log(obj.name);
    // => 此时此地GS

这个get 或 set 属性无法和 writable 或 value 属性共存,否则报错。为什么呢?大概就是不可能定一个属性即可以对它进行正常读写,又要在它上面架设一层getter/setter来进行访问改写。

    var obj={}
    var temp
    Object.defineProperty(obj,'name',{
      value:'此时此地GS',
      writable:true,
      get:function(){
        return temp
      },
      set:function(val){
        temp = val
      }
    })
    // => Cannot both specify accessors and a value or writable attribute

总结

以上就是Object.defineProperty()中descriptor属性描述符的详细使用解读,如果需要对obj中prop的多个属性值进行精细控制,就需要用到Object.defineProperties(),具体某个属性值的描述符内的使用方法同本文内容。

感谢观看,不足之处欢迎批评指正!