Object.defineProperty函数的configurable配置

以前一直觉得Object.defineProperty是个非常普通的点,无非就是配置一下属性描述符,包括对象是否可枚举(enumerable)、可写(writable)之类的,然后就是定义一个get, set这种。而在实际写代码的过程中,这个东西用到的频率不大,更多的是出现在一些库的源码里头。最近注意到一个以前漏掉的点,就是属性是否可配置,configurable这个属性。

以下是MDN对configurable的介绍:

configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。

可见它不仅控制了该属性是否可被删除,而且控制了属性描述符是否能改变

首先我们来看第一点:

Object.defineProperty(o, 'key', {
    value: '1',
//     如果不指定这些属性描述符,默认都是false
//     configurable: false,
//     writable: false,
//     enumerable: false
})
console.log(o.key) // 1
console.log(delete o.key) // false 尝试两种方案删除属性
console.log(Reflect.deleteProperty(o, 'key')) // false
console.log(o.key) // 1 由于不可配置,属性未被删除

使用delete操作无法从o上去掉key这个属性

关于第二点,MDN也有一些描述:

configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。

在例子中,我们首先直接去修改value

var o = {}
Object.defineProperty(o, 'key', {
    value: '1',
//    writable: true
//    configurable: false // 默认
})
console.log(o.key) // 1

Object.defineProperty(o, 'key', {
    value: '2'
}) // 报错 Uncaught TypeError: Cannot redefine property: key at Function.defineProperty ()
console.log(o.key) // 1

毫无疑问,这会报一个错误。
接下来我们设置writable: true

var o = {}
Object.defineProperty(o, 'key', {
    value: '1',
	writable: true,
//    configurable: false // 默认
})
console.log(o.key) // 1

Object.defineProperty(o, 'key', {
    value: '2'
})
console.log(o.key) // 2 可以被修改

如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。
当试图改变不可配置属性(除了value和writable 属性之外)的值时会抛出TypeError,除非当前值和新值相同。

var o = {}
Object.defineProperty(o, 'key', {
    value: '1',
	writable: true
})
console.log(o.key) // 1

Object.defineProperty(o, 'key', {
    writable: false,
    value: '2'
})
console.log(o.key) // 2 此时还可写

Object.defineProperty(o, 'key', {
    writable: true,
    value: '3'
}) // TypeError, writable只能单向从true变为false, 反向会报错
console.log(o.key)

或者我们从value + writable这种数据描述符,转换到get + set这种存取描述符,也会报错:

var o = {}
Object.defineProperty(o, 'key', {
    value: '1',
	writable: true
})
console.log(o.key) // 1

Object.defineProperty(o, 'key', {
    get () {
        return '2'
    }
}) // TypeError
console.log(o.key)

从一个存取描述符,转换到另一个存取描述符,也是不可以的:

var o = {}
Object.defineProperty(o, 'key', {
    get () {
    	return '1'
    }
})
console.log(o.key) // 1

Object.defineProperty(o, 'key', {
    get () {
        return '2'
    }
}) // TypeError
console.log(o.key)

综合以上分析,可见如果让一个属性不可配置,它无法被delete删除,其次重新利用Object.defineProperty定义属性时会受到非常大的限制。那么可以怎样利用这个属性呢?我们知道js库里面是极喜欢使用Object.defineProperty的,那么对configurable: false也会有一些处理。

举个栗子,拿vue2.6的源码看下:

// vue\src\core\observer\index.js
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return // 属性不可配置,直接返回,后面就不执行了
  }
  ...
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true, // 如果上面不对property.configurable === false的情况 进行判断,这里就会报错
    get: function reactiveGetter () {
      ...
    },
    set: function reactiveSetter (newVal) {
      ...
    }
  })

defineReactive是vue响应式的核心方法,它对objkey属性定义了getset 操作,从而实现了数据的监听和响应。从以上代码可知,如果被监听的对象objkey属性不可被配置,则函数直接返回,数据无法被监听。那么我们可以合理地定义一个对象,让它的某些属性无法被vue监听,下面是一个栗子~。

export default {
  name: 'Few',
  data () {
    return {
      info: (function () {
        let tmp = {
          name: 'Rousould Miccson',
          address: 'Mountain Goo'
        }
        Object.defineProperty(tmp, 'loveRates', {
          configurable: false,
          enumerable: true,
          writable: true,
          value: {
            sing: 1,
            walk: 4,
            soccer: 3
          }
        })
        return tmp
      }())
    }
  },
  watch: {
    'info.name': function (newVal, oldVal) {
      console.log(newVal, oldVal)
    },
    'info.loveRates': {
      deep: true,
      handler: function (newVal, oldVal) {
        console.log(newVal, oldVal)
      }
    }
  },
  mounted () {
    console.log(this)
    console.log(Object.getOwnPropertyDescriptors(this.$data.info))
    this.info.name = 'Jocky'
    this.info.loveRates.sing = 3
  }
}

我们在一个组件的data中定义了info对象,并把loveRates属性设置为不可配置,在mounted钩子中执行分别改变nameloveRates.sing,从最后的打印结果看,loveRates属性的确没有被监听,这里就不截图了。如果以后遇到很大的对象,而有部分内容不想被监听,可是使用这种方式处理,减少被监听元素的个数,从而提高vue的运行性能。

你可能感兴趣的:(Javascript,前端)