以前一直觉得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响应式的核心方法,它对obj
的key
属性定义了get
和set
操作,从而实现了数据的监听和响应。从以上代码可知,如果被监听的对象obj
的key
属性不可被配置,则函数直接返回,数据无法被监听。那么我们可以合理地定义一个对象,让它的某些属性无法被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
钩子中执行分别改变name
和loveRates.sing
,从最后的打印结果看,loveRates
属性的确没有被监听,这里就不截图了。如果以后遇到很大的对象,而有部分内容不想被监听,可是使用这种方式处理,减少被监听元素的个数,从而提高vue的运行性能。