JTangming / blog

My repository on GitHub.
Other
53 stars 0 forks source link

Javascript 之数据劫持 #15

Open JTangming opened 5 years ago

JTangming commented 5 years ago

每当收到候选人简历中有 VUE 的免不了提问“如何实现数据的双向绑定”,往往很多人都是浅尝辄止,简单来说,就是通过以下步骤实现双向绑定:

所以 VUE 双向绑定关键的一环就是数据劫持(Vue 2.x 使用的是 Object.defineProperty(),而 Vue 在 3.x 版本之后改用 Proxy 进行实现),数据劫持即在访问或修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作如修改返回结果,以下专门梳理一下 Javascript 的数据劫持。

Object.defineProperty

通过 Object.defineProperty() 来劫持对象属性的 setter 和 getter 操作,在数据变动时做你想要做的事情,示例代码如下:

Object.defineProperty(obj, key, {
    get()  {
        // 相关操作,最终 return
    },
    set(newVal) {
        // 一些 handle 操作
    }
})

Object.defineProperty 有它存在的问题,比如不能监听数组的变化,对 object 劫持必须遍历每个属性,可能存在深层次的嵌套遍历。那还有没有比 Object.defineProperty 更好的实现方式呢?

Proxy

在上一篇文章中已经有提及到 Proxy。

Proxy 的构造函数能够使用代理模式,即 let proxy = new Proxy(target, handler);,Proxy 构造函数中的两个参数具体是:

Proxy 其内部功能十分强大的,有13种数据劫持的操作,如get、set、has、ownKey(获取目标对象的所有 key)、deleteProperty等,下面主要梳理 set、get。

get

get 即在获取到某个对象属性值的时候做预处理的方法,其有两个参数:target、key,示例代码如下:

let Obj = {};
let proxyObj = new Proxy(Obj, {
    get: function(target, key) {
        // 如通过 target 判断某个属性是否符合预期
        if (target[‘xx’] > 80) {return “该考生优秀!”}
        else if (!/^[0-9]+$/.test(key)) {return “学号格式不对!”}
        return Reflect.get(target, name, receiver); // Reflect 见文章最后的总结
    }
});

set

set 即用来拦截某个属性赋值操作的方法,可以接受四个参数:

还是沿用上面的例子,比如一个考试成绩录入的校验,代码如下:

let validator = {
  set: function(target, key, value) {
    if (key === 'score') {
      if (!/^[0-9]+$/.test(value)) {
        throw new TypeError(‘分数必须为整数');
      }
      if (value > 100) {
        throw new TypeError(‘成绩满分为 100');
      }
    }
    // 对于满足条件的属性直接写入
    target[key] = value;
  }
};
let proxy = new Proxy(obj, handler);
// ...

Proxy 相对 Object.defineProperty,它支持对数组的数据对象的劫持,不用像 VUE 那样要对数据劫持的话,需要进行重载 hack。对于以上提到的嵌套问题,Proxy 可以在 get 里面递归调用 Proxy 即返回下一个”代理“来完成递归嵌套,可以说 ES6 的 Proxy 就是对 Object.defineProperty 的改进和升级。关于嵌套举例如下:

let obj = {
  0801080132:  {
      name: ‘Jason',
      score: 99
  },
  // …
};
let handler = {
  get (target, key, receiver) {
    // 如果属性值不为空或者是一个对象,则继续递归
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return xxx // 返回业务数据
  }
}
let proxy = new Proxy(obj, handler)

Proxy 本质上就是在数据层外加上代理,通过这个代理实例访问里边的数据,这就是 Proxy 实现数据劫持的方式。总结一下: