JS基础——Object.defineProperty

 阅读约 8 分钟

Object.defineProperty()方法可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

/*
* obj 要定义属性或修改属性的目标对象。
* prop 属性名称
* descriptior 属性描述符
*/
Object.defineProperty(obj, prop, descriptor)

属性描述符

  • configurable 对象是否可通过Object.defineProperty修改,默认false
  • enumerable 能否枚举(for..in 或 Object.keys),默认false
  • writable 只有为true才能通过赋值来修改值,默认false
  • value 属性的值,默认undefined
  • get 当访问该属性时,该方法会被执行,默认undefined
  • set 当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认undefined

(value或writable)和(get或set)不能同时存在

var a = {};
Object.defineProperty(a,'name',{configurable : false})
Object.defineProperty(a,'name',{value : 'xuriliang'})  //会抛出异常,如果configurable为true则不会
a.name = 'rlxu' // a得值不会发生改变,因为writable默认为false
Object.keys(a)  //没有获取到name,因为enumerable默认为false

定义属性

通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。

var obj = {};
Object.defineProperty(obj,'name',{
    configurable : true,
    writable : true,
    enumerable : true,
    value : 'xuriliang'
})
Object.keys(obj)  //enumerable为true,可枚举
obj.name = 'rlxu'; //writable为true,可赋值

同时定义多个属性可以使用Object.defineProperties,如:

var obj = Object.create(null)
Object.defineProperties(obj, {
  '_id': {
    value: 1,
    configurable : true,
    writable : true,
    enumerable : true
  },
  'name': {
    value : 'Lucy',
    configurable : true,
    writable : true,
    enumerable : true
  }
});

修改现有属性

仅当属性描述configurable为true时,才可以修改属性。(通过赋值操作添加的普通属性configurable、enumerable、writable默认为true)

var obj = { name : 'xuriliang'}
var showLog = function(newval){
    console.log('name change :'+newval)
}
Object.defineProperty(obj,'name',{
    enumerable: true,
    configurable: true,
    set : function(newval){
        showLog(newval)
    }
})
obj.name = 'rlxu';

vue中的应用

1、把data中的属性代理到vm实例上,如:

function MyVue(options){
    let data = this._data = options.data || {};
    Object.keys(data).forEach(key => {
        Object.defineProperty(this,key,{
          enumerable: true,
          configurable: true,
          get: function(){
            console.log('get execute...')
            return this._data[key]
          },
          set: function(newval){
            console.log('set execute...')
            this._data[key] = newval
          }
        })
    })
}
let v1 = new MyVue({
  data: {
    message: '你好',
    borth:{
      age: 18
    }
  }
})

这样就可以通过v1.message去访问data属性。需要注意的是,当我们设置v1.borth.age = 20会发现,get执行了一次,set没有执行。这是因为我们只重新定义了message和borth属性,get执行了一次是因为访问了borth,set没有执行时因为我们没有定义age的setter。没有设置会采用对象的默认行为。
2、当对象值发生改变触发操作,如:

function observe(val){
  Object.keys(val).forEach(key => {
     defineReactive(val,key)
  })
}

function defineReactive(obj,key){
    let val = obj[key]
    if(Object.prototype.toString.call(val) === '[object Object]'){
        observe(val)
        return
    }
    Object.defineProperty(obj,key,{
      get: function(){
            return val
      },
      set: function(newval){
        if(val == newval)
            return
        console.log(`oldval:${val} newval:${newval}`)
        val = newval
      }
    })
}

function MyVue(options){
    let data = this._data = options.data || {};
    //把data代理到vm实例上
    Object.keys(data).forEach(key => {
        Object.defineProperty(this,key,{
          enumerable: true,
          configurable: true,
          get: function(){
            console.log('vm get exec...')
                return this._data[key]
          },
          set: function(newval){
            console.log('vm set exec...')
            this._data[key] = newval
          }
        })
    })
    //监听data的值发生改变
    observe(data)
}
let v1 = new MyVue({
    data: {
        message: '你好',
        borth:{
            age: 18
        }
    }
})

当我们修改data里的值时会触发set

参考资料

MDN

阅读 346更新于 2019-10-19

推荐阅读
目录