Vue3.0版本会将数据劫持的方式从Object.defineProperty切换为Proxy,所以找了时间重新回顾了一下属性描述,并了解了以下Proxy
1. Object.defineProperty
给对象的属性设置属性描述,接受三个参数
/*
* obj:需要定义属性的对象
* prop:定义描述的属性名
* descriptor: 属性描述
*/
Object.defineProperty(obj, prop, descriptor);
1.1 descriptor
用于描述属性的对象,可以包含以下值
1.1.1 get
属性在获取值的时候会调用该方法
var obj = {}
Object.defineProperty(obj, 'a', {
get() {
console.log('get a');
return 1;
}
})
console.log(obj.a)
// 优先输出'get a', 之后输出:1
1.1.2 set
属性设置值的时候会调用该方法
var obj = {}
Object.defineProperty(obj, 'a', {
set(value) {
console.log(`set:${value}`);
}
})
obj.a = 1
// 输出 `set:1`
1.1.3 value
用于设置对象属性的初始值,无法同get和set方法同时设置
var obj = {}
Object.defineProperty(obj, 'a', {
value: 1
})
console.log(obj.a) // 1
1.1.4 enumerable
设置属性是否可以枚举,用于for in枚举属性时候是否可以获取
var obj = {}
Object.defineProperty(obj, 'a', {
value: 1,
enumerable: false, // 设置false无法通过for in获取
})
console.log(obj.a) // 1
for(let key in obj) {
console.log(`key:${key}`) // 不会执行
}
1.1.5 configurable
设置属性是否可以再次定义属性描述
var obj = {}
Object.defineProperty(obj, 'a', {
value: 1,
configurable: false, // 设置false无法再次配置属性
})
Object.defineProperty(obj, 'a', {
value: 2,
})
// 抛出异常:Uncaught TypeError: Cannot redefine property 'a'
PS:对于已有属性可以修改value和enumerable和writable的值(例如:如果obj = {a: 1}这里修改属性a的这三个值不会报错)
1.1.6 writable
设置属性是否可以赋值,无法同get和set方法同时设置
var obj = {}
Object.defineProperty(obj, 'a', {
value: 1,
writable: false, // 设置false,无法被普通赋值
})
obj.a = 2
console.log(obj.a) // 1
1.2 Object.preventExtensions()
阻止对象扩展新的属性,不过并不限制对象原型上的属性扩展
var obj = {a: 1}
Object.preventExtensions(obj)
obj.b = 2 // 严格模式下抛出TypeError异常
console.log(obj) // {a: 1}
可以使用Object.isExtensible(obj)来判断是否已经阻止扩展了
1.3 Object.seal()
将使得对象禁止扩展属性,同时禁止现有属性的configurable
var obj = {a: 1}
Object.seal(obj)
console.log(Object.isExtensible(obj)) // false
Object.defineProperty(obj, 'a', {
value: 2
}) // 抛出异常
相当于Object.preventExtensions的基础上,将已有属性的configurable都设置为false
var obj = {a: 1}
Object.preventExtensions(obj)
Object.defineProperty(obj, 'a', {
configurable: false
})
console.log(Object.isSealed(obj)) // true
可以使用Object.isSealed(obj)判断是否属性属于该情况
1.4 Object.freeze()
在Object.seal()的基础上,将属性的writable设置为false
var obj = {a: 1}
Object.freeze(obj)
console.log(Object.isSealed(obj)) // true
obj.a = 2
console.log(obj.a) // 1
相当于
var obj = {a: 1}
Object.seal(obj)
Object.defineProperty(obj, 'a', {
writable: false
})
console.log(Object.isFrozen(obj)) // true
可以使用Object.isFrozen(obj)判断是否属性属于该情况
2. Object.defineProperties
同Object.defineProperty,可一次性批量定义多个对象属性
Object.defineProperties(obj, {
prop1: {
get() {}
set() {}
...
},
prop2: {
get() {}
set() {}
...
}
})
3. Proxy
使用Proxy可以创建一个对象的代理
2.1 new Proxy()
使用new Proxy(target, handler)可以创建对象target的proxy对象,操作proxy对象的时候,根据设置的handler,可以设置对象操作的各时期的具体操作
getPrototypeOf():在调用Object.getPrototypeOf()的时候setPrototypeOf():在使用Object.setPrototypeOf()的时候isExtensible():在使用Object.isExtensible()的时候preventExtensions():在使用Object.preventExtensions()的时候getOwnPropertyDescriptor():在使用Object.getOwnPropertyDescriptor()的时候defineProperty():在使用Object.defineProperty()的时候has():在使用in操作符的时候get():在获取属性值的是欧set():在设置属性值的时候deleteProperty():在delete删除属性的时候ownKeys():在Object.getOwnPropertyNames()和Object.getOwnPropertySymbols()的时候apply():在对象作为方法调用的时候construct():在使用new操作符的时候
以set举例说明:
var obj = {}
var proxy = new Proxy(obj, {
set (target, prop, value) {
console.log('set value')
target[prop] = value
}
})
proxy.a = 1 // 'set value'
console.log(obj) // {a: 1}
操作proxy对象可以修改对应对象的属性信息,但是直接操作target对象,并不会触发proxy对象中设置的操作:
var obj = {}
var proxy = new Proxy(obj, {
set (target, prop, value) {
console.log('set value')
target[prop] = value
}
})
obj.a = 1 // 并不会输出任何信息
console.log(obj) // {a: 1}
2.2 Proxy.revocable()
创建一个可以revocable对象,可以在需要废弃proxy对象的时候销毁
var revocable = Proxy.revocable({}, {
set (target, prop, value) {
console.log('set value')
target[prop] = value
}
})
revocable.proxy.a = 1 // 'set value'
revocable.revoke() // 销毁对象
revocable.proxy.a = 2 // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
4. 总结
4.1 相同点
从目的来看Proxy和defineProperty都是为了扩展对象的特性,如果要用来实现MVVM,两种方案都可以完成
4.2 不同点:
从三个方面来说明
- 作用目标不同:
defineProperty主要是用于对象定义属性,注重的是设置对象中属性的描述,而Proxy用于处理对象,注重的是对象的相关操作 - 操作目标不同:
defineProperty的时候,是需要直接操作对象本身,来触发相关属性设置,而Proxy则需要操作new创建的proxy对象,对原对象操作并不会触发相关内容 - 提供的触发事件不同:
defineProperty只提供了set,get方法可以作为切入口,而Proxy提供了更丰富的对象操作切入口
总的来说vue3.0使用Proxy的目的在于对对象劫持的时候,不用遍历所有属性,可以直接使用对象的proxy对象,同时在对象追加属性的增加劫持的时候,不用再手动使用$set添加劫持
当然和Proxy密切相关的Reflect,这个就留在下次再说了
6. 参考
本文存在的问题还望各位指正,谢谢