是什么:
Proxy 用于修改某些操作的默认行为,是在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
为什么:
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
proxy 模式一般可以使用在当你想要:拦截或者控制对一个对象的访问时,通过掩盖程序或隐藏逻辑来降低方法/类的复杂度,阻止没有经过验证/准备的重资源操作。
怎么做:
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
Proxy 实例的方法
1 get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
let person = {
name: 'lxf'
}
let proxyPersonGet = new Proxy(person, {
get: function (target, property) {
console.log(property);
return target[property]
}
})
let child = Object.create(proxyPersonGet);
console.log(proxyPersonGet.name) // name lxf
console.log(child.name) // name lxf
get方法可以继承。上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。
2 set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
let proxyPersonSet = new Proxy({}, {
set: function (obj, prop, value) {
console.log(prop)
if (prop === 'age') {
if(value <= 200){
obj[prop] = value;
}else{
console.log('年龄不正常!');
}
}else{
obj[prop] = value;
}
}
})
proxyPersonSet.age = 210; //age 年龄不正常!
上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
3 apply()
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
let target = function () {
return 'I am target!'
}
let handler = {
apply: function () {
return "I am proxy!"
}
}
console.log(target()); //I am target!
let p = new Proxy(target, handler);
console.log(p()); //I am proxy!
4 has()
has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has方法可以接受两个参数,分别是目标对象、需查询的属性名。
下面的例子使用has方法隐藏某些属性,不被in运算符发现。
let person = new Proxy({
name:'lxf'
}, {
has: function (target, key) {
if (key[0] === '_') {
console.log(key);
return false;
} else {
console.log(key);
return true;
}
}
});
console.log('_age' in person) //_age false
console.log('name' in person) //name true
上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。
另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。
5 deleteProperty()
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
let person = {
name: 'lxf'
},
handler = {
deleteProperty: function (target, key) {
console.log(key);
delete target[key];
return true;
}
}
let proxy = new Proxy(person, handler);
delete proxy.name;
console.log(proxy) // name {}
其他实例方法就不在一一赘述,可以参考es6.ruanyifeng.com/#docs/proxy