ES6-Proxy Reflect 入门学习

1,069 阅读4分钟

ProxyReflect是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);
  }
  })

完整demo

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 等方法,支持对象属性的动态添加和删除,极大的简化了响应化的代码量。