Proxy
和Reflect
是ES6中新增的用来操作对象的API。
Proxy
- Proxy原意是代理的意思,这里表示由它来“代理”某些操作,也称为“代理器”。
- Proxy可以理解成在目标对象前假设一层“拦截层” ,外界对该对象的访问都必须先通过这层拦截,因此也提供了一种机制,可以对外界的访问就行过滤和改写。也可以理解为“数据劫持”。
Proxy基础语法
proxy一共可以可以代理(劫持或叫trap)13种对象的操作,如下:
- 常用:set、get、has、deleteProperty、apply
- 其他:getPrototypeOf、setPrototypeOf、isExtensible、preventExtensions、getOwnPropertyDescription、defineProperty、ownKeys、construct
Proxy基础应用
let test = {
name: 'zyh',
boy: {
name: 'zy',
age:0
}
}
var obj = new Proxy(test,{
get: function(target, key, receiver){
console.log('getting')
return Reflect.get(target, key, receiver)
},
set: function(target, key, value, receiver){
console.log('setting')
return Reflect.set(target, key, value, receiver)
}
})
obj.count = 1
// setting
上面的例子可以看作Proxy重载(overload)了点运算符。
函数也是一个对象,so,Proxy也可以代理一个函数。
var func = (a, b)=> {
return a + b
}
var fproxy = new Proxy(func, {
apply: function (target, ctx, args) {
console.log('do sth')
return target(...args)
}
})
- Proxy 能够让我们以简洁易懂的方式控制外部对对象的访问。功能类似于设计模式中的代理模式。
- 某些时候我们可以把一些非核心逻辑交给Proxy处理,例如访问日志记录、访问控制、属性验证等,达到关注点分离的目的。
表单验证demo
// 校验工具函数
var isNotEmpty = value => value !== '';
var isNumber = value => /^[0-9]*$/.test(value);
var isBetween = (value, min, max) => {
if (max === undefined) {
max = Number.MAX_VALUE;
}
if (min === undefined) {
min = Number.MIN_VALUE;
}
return value > min && value < max;
}
// 校验器
let validators = {
name: [{
validator: isNotEmpty,
errorMsg: '姓名不能为空'
}],
age: [
{
validator: isNumber,
errorMsg: '年龄必须为数字'
},
{
validator: isBetween,
errorMsg: '年龄必须为大于 0 并且小于 100',
params: [0, 100]
}
]
}
var validatorCreater = (target, validator) => new Proxy(target, {
// 保存校验器
_validator: validator,
set(target, key, value, receiver) {
// 如果赋值的属性存在校验器,则进行校验
if (this._validator[key]) {
// 遍历其多个子校验器
for (validatorStrategy of this._validator[key]) {
let { validator, errorMsg = '', params = [] } = validatorStrategy;
if (!validator.call(null, value, ...params)) {
throw new Error(errorMsg);
return false;
}
}
}
// 赋值语句放最后,如果失败不赋值,如果不存在校验器则赋值
return Reflect.set(target, key, value, receiver);
}
})
Proxy进阶
Proxy如果劫持了数组的set方法,那么是可以检测到数组的变化的,这一点比它的前辈Object.defineProperty
算是进步的,但Proxy本身也是不支持复杂对象(对象的值也是对象)的代理,so如果你要劫持一下复杂对象的方法,那需要自己用递归处理一下。
const obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'postcss']
}
}
let handler = {
get(target, key, receiver) {
console.log('get', key)
// 递归创建并返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
上面例子中的obj
就是个复杂对象,举个例子,当我们执行obj.info.name = 2
时,控制台输出如下:
我们可以把上面这行代码拆成两步走:1)
obj.info
,这是因为我们劫持了obj的get方法,此时发现我们要取的这个值是个对象,这里就会递归调用new Proxy()
来把这个对象也代理一下。2)obj.info.name=2
这时因为我们设置了info这个代理对象的name值,所有自然会被打印出set的日志。这个例子还是很直观的告诉我们proxy是如何代理复杂对象的。完整例子
Reflect
其实上面的例子中,我们已经用到了Reflect,Reflect对象某种意义上也可以说是为Proxy准备的,Proxy的handler中的各个trap都有对应的Reflect的同名方法。Reflect与Proxy搭配使用,可以让Proxy对象方便地调用对应的Reflect方法完成默认行为,作为修改行为的基础。
下面简单介绍一下:
Reflect基础
- Reflect是一个内置对象,和Proxy一样也是ES6为了操作对象提供的新API。
- Reflect不是一个函数对象,没有构造函数。不能与new运算符一起使用。
- Reflect的所有属性和方法都是静态的(像Math对象一样)
小结
- Proxy让开发者很方便地使用代理模式,业务逻辑清晰。
- Proxy 不但可以取代 Object.defineProperty 并且还扩增了非常多的功能。Proxy 技术支持监测数组的 push 等方法,支持对象属性的动态添加和删除,极大的简化了响应化的代码量。