proxy
proxy 的定义是,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。其重点就是监听,vue3的响应式抛弃object.defineProperty,使用proxy也是看中了其在拦截响应上的优秀特质。
一、proxy的使用
const p = new Proxy(target, handler)
target:被代理的对象
handler:捕获器
尝试一下:
let obj = {
name:"cici",
age:18
}
const proxyObj = new Proxy(obj,{})
console.log(proxyObj)
//{ name: 'cici', age: 18 }
这里是可以出来obj的值的,所以Proxy对象具体源对象的属性。但是它还包括proxy其他的相对应的属性。
目标对象的内部的this指向会自动改为proxy代理对象
二、捕获器
handler捕获器,常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
this 上下文绑定在handler 对象上,并不是绑定在proxy对象上
1.handler.get()
handler.set() : 拦截对象的读取属性操作
只要读取对象的值就会进入 get方法
let getObj = {
text:"hello"
}
let getProxy = new Proxy(getObj,{
get(target, property, receiver){
`target`:目标对象。
`property`: 被获取的属性名。
`receiver`: Proxy 或者继承 Proxy 的对象
}
})
console.log(getObj.text)
2.handler.set()
handler.set() : 拦截设置属性值的操作
let setProxy = new Proxy({},{
get(target, property, value,receiver){
`target`:目标对象。
`property`: 被获取的属性名。
`value`:赋予的新值
`receiver`: Proxy 或者继承 Proxy 的对象
}
})
set()方法应当返回一个布尔值。返回为true代表设置成功,如果为false,应该抛出一个异常- 如果目标属性没有配置存储方法,即
[[Set]]属性的是undefined,则不能设置它的值
let setProxy = new Proxy({}, {
set: function(target, prop, value, receiver) {
try {
target[prop] = value;
return true;
}catch{
console.error(`${prop} form source is not defined`)
}
}
})
p.a = 10;
3.has()
针对in 操作符的代理方法
let p = new Proxy(target, {
has: function(target, prop) {
`prop`:检查是否存在的属性
}
});
has方法 会返回一个 boolean值
let setProxy = new Proxy({},{
set: function(target, prop, value, receiver) {
try {
target[prop] = value;
console.log('property set: ' + prop + ' = ' + value);
return true;
}catch{
console.error(`${prop} form source is not defined`)
}
},
has(target,prop){
console.log(`porperty has function`)
}
})
console.log('name' in setProxy)
//porperty has function
//false
4.handler.construct()
用于拦截new操作符
let p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
`argumentsList`:参数列表
`newTarget`:最初被调用的 构造函数
}
});
必须返回一个对象 Proxy 初始化时,传给它的
target必须具有一个有效的 constructor 供new操作符调用。
function animals(type){
this.type = type
}
const animalsProxy = new Proxy(animals,{
construct(target,args){
console.log("this is animalsProxy constractFn")
console.log(args)
return new target(...args);
}
})
let a1 = new animalsProxy("dogs")
5.handler.deleteProperty()
用于拦截对对象属性的delete操作
var p = new Proxy(target, {
deleteProperty: function(target, property) {
`property`:待删除的属性名
}
});
与has类似,deleteProperty 必须返回一个Boolean 类型的值,表示了该属性是否被成功删除。
6.其他
| 方法 | 作用 | 返回值 |
|---|---|---|
handler.defineProperty() | 拦截对象的Object.defineProperty() | boolean |
handler.getPrototypeOf() | 读取代理对象的原型 | 一个对象或者 null |
handler.apply()handler.getOwnPropertyDescriptor()handler.isExtensible()handler.ownKeys()handler.preventExtensions()handler.setPrototypeOf()
三、使用
1.转发数据
let t1 = Object.create(null)
let t1Proxy = new Proxy(t1, {});
t1Proxy.a = 37; //
console.log(t1.a); // 37
2.拦截验证
let validProxy = new Proxy({}, {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// The default behavior to store the value
obj[prop] = value;
// 表示成功
return true;
}
});
validProxy.age = 100;
console.log(person.age);
// 100
validProxy.age = 'young';
// 抛出异常:Uncaught TypeError: The age is not an integer
validProxy.age = 300;
// 抛出异常:Uncaught RangeError: The age seems invalid
四、总结
与Object.defineProperty对比,对数据监听更加优秀与便利
使用Object.defineProperty对象监听,其实并不能对一个对象的修改、删除等操作进行很好的监听。defineProperty只能对具体的属性值,在对象的属性层面进行响应式的监听。在vue2中,通过递归进行处理,使其性能并不很完美。在vue2中对数组的监听,是vue重写了数组的操作方法,例如 push、splice等,触发此方法后,进行数据更新。而对未进行数据绑定的属性也需要使用$set添加监听。
let obj = {
name:"cici",
age:18
}
for(let i in obj){
let val = obj[i]
Object.defineProperty(obj,i,{
get(){
console.log("this is getter Func")
return val
},
set(v){
console.log("this is setter Func")
val = v * 2
}
})
}
console.log(obj.name)
obj.age = 6
console.log(obj.age)
/**
this is getter Func
cici
this is setter Func
this is getter Func
12
**/
上述只是一个简单的例子来说明一下使用原理。在vue3中则是使用了Proxy&Reflect进行实现。