之前学习了 Object.defineProperty ,Vue2.0 中 v-model就是由Object.defineProperty原理,那现在我们来学习下Vue3.0中启用的Proxy代理,就是Vue3中数据劫持原理。
啥是Proxy呢
Proxy 对象是ES6新出的一个特性,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
- Object.defineProperty只能劫持对象的属性,不能监听数组。也不能对 es6 新产生的 Map,Set 这些数据结构做出监听。也不能监听新增和删除操作等等。
- Proxy可以直接监听整个对象而非属性,可以监听数组的变化,具有多达13中拦截方法。
Proxy有两个参数 target handler
- target:为包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- handler:为具体操作,其实就是一个对象,其属性是当执行一个操作时定义代理的行为的函数。就是说里面写各种拦截的函数。不同的拦截方法拦截的是不同的操作
最主要就是那些拦截方法的使用,下面我们来试试看吧!
拦截方法
get方法
与之前Object.defineProperty类似
get(target, property, receiver)方法用于拦截某个属性的读取操作,可以接受三个参数,分别为目标对象、属性名和 proxy 实例本身
let person = { name: "ms", age: "20" };
// 给person设置一个代理
let p = new Proxy(person, {
get(target, property) {
console.log("我拦截了" + target + "读取" + property);
console.log("它的值为" + target[property]);
// 定义你要返回的值
return target[property];
},
});
console.log(p.age); //20
console.log(p.name); //jack
我们拦截住了age,name之后我们可以想返回什么,在后面进行return即可
set方法
set(target, property, value, receiver)方法用来拦截某个属性的赋值操作,四个参数依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选
let person = { name: "ms", age: "20" };
// 给person设置一个代理
let p = new Proxy(person, {
get(target, property) {
return target[property];
},
set(target, property, value) {
console.log("要设置对象属性?我拦截到了~");
console.log("要修改成" + value + "?");
console.log("留下来,这个属性,不能等于这个值");
target[property] = 666;
},
});
p.age = 777
console.log(p.age);
console.log(p.name);
拦截掉了,也成功打印出来了 ,但是报错了。。。
TypeError: 'set' on proxy: trap returned falsish for property 'age'
这,这是啥意思呀? 查询过后,发现 定义 Proxy 代理对象的 set 的时候, 要返回 return true
let person = { name: "ms", age: "20" };
// 给person设置一个代理
let p = new Proxy(person, {
get(target, property) {
return target[property];
},
set(target, property, value) {
console.log("要设置对象属性?我拦截到了~");
console.log("要修改成" + value + "?");
console.log("留下来,这个属性,不能等于这个值");
target[property] = 666;
return true
},
});
p.age = 777
console.log(p.age); //20
console.log(p.name); //jack
成功运行,原来set是要返回一个布尔值的。
has方法
has(target, propKey) 拦截的操作,返回⼀个布尔值
let range = {
start: 1,
end: 5,
}
let r = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end;
}
})
console.log(3 in r) //true
console.log(6 in r) //false
deleteProperty方法
deleteProperty(target, propKey)拦截的操作,返回⼀个布尔值。delete proxy[propKey],就是拦截删除时的操作。
let person = {
name: 'ms',
age: 26,
}
let p = new Proxy(person, {
deleteProperty(target, prop) {
delete target[prop]
return true
}
})
delete p.age;
console.log(person) //{name:"ms"}
ownKeys方法
ownKeys(target):拦截循环,返回⼀个数组。ownKeys 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbol(proxy)、Object.keys(proxy)、 for.....in循环
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
ownKeys(target) {
return Object.keys(target).filter(item => {
return !item.startsWith('_')
})
}
})
console.log(Object.getOwnPropertyNames(p)) //["username", "age"]
console.log(Object.getOwnPropertySymbols(p)) //[]
console.log(Object.keys(p)) //["username", "age"]
for (const key in p) {
const element = p[key];
console.log(element) //ms 18
}
getOwnPropertyDescriptor方法
getOwnPropertyDescriptor(target, propKey):拦截,返回属性的描述对象。拦截 Object.getOwnPropertyDescriptor(proxy, propKey)
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
getOwnPropertyDescriptor(target, propKey){
return Object.getOwnPropertyDescriptor(target,propKey)
}
})
console.log(Object.getOwnPropertyDescriptor(p,'age'))
//{configurable: true,enumerable: true,value: 18,writable: true}
defineProperty方法
defineProperty(target, propKey, propDesc)拦截 Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs) 返回⼀个布尔值。
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
defineProperty(target, propKey, propDesc){
console.log(propKey,propDesc)
return true
}
})
console.log(Object.defineProperty(p, 'username', {
value:'mss'
}))
console.log(Object.defineProperties(p,{
"age": {
value: 30,
},
}))
preventExtensions方法
preventExtensions(target) 拦截,返回⼀个布尔值。
Object.preventExtensions(proxy)
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
preventExtensions(target){
Object.preventExtensions(target);
return true;
}
})
console.log(Object.preventExtensions(p))
getPrototypeOf方法
getPrototypeOf(target):拦截返回⼀个对象。
Object.getPrototypeOf(proxy)
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
getPrototypeOf(target){
return Object.getPrototypeOf(target)
}
})
console.log(Object.getPrototypeOf(p))
isExtensible方法
isExtensible(target) 拦截返回⼀个布尔值。
Object.isExtensible(proxy)
let userinfo = {
username: 'ms',
age: 18,
_password: '***',
[Symbol()]: '我是唯一的',
};
let p = new Proxy(userinfo, {
isExtensible(target){
Object.isExtensible(target)
return true
}
})
console.log(Object.isExtensible(p)) //true
setPrototypeOf方法
setPrototypeOf(target, proto)拦截返回⼀个布尔值。如果⽬标对象是函数,那么还有两种额外操作可以拦截。
Object.setPrototypeOf(proxy, proto)
let proto = {};
var p = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
console.log(Object.getPrototypeOf(p) === proto) // true
apply方法
apply 拦截函数的调用、call和apply操作 apply(target, object, args):
- proxy(...args)
- proxy.call(object, ...args)
- proxy.apply(...)
let sum = function(...args) {
let count = 0;
args.forEach((item) => {
count += item
})
return count;
};
let p = new Proxy(sum, {
apply(target, ctx, args) {
return target(...args) * 2
}
})
console.log(p(1, 2, 3, 4)) //20
console.log(p.call(null, 1, 2, 3, 4)) //20
console.log(p.apply(null, [1, 2, 3, 4])) //20
construct方法
construct(target, args)拦截 实例作为构造函数调⽤的操作
let User = class {
constructor(name) {
this.name = name;
}
}
User = new Proxy(User, {
construct(target, args, newTarget) {
return new target(...args)
}
})
console.log(new User('ms')) //User {name: "ms"}
Proxy的作用
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理