Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript 之数据劫持 #15

Open
JTangming opened this issue Jun 1, 2019 · 0 comments
Open

Javascript 之数据劫持 #15

JTangming opened this issue Jun 1, 2019 · 0 comments

Comments

@JTangming
Copy link
Owner

JTangming commented Jun 1, 2019

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

  • 监听器 Observer,用来劫持所有属性变动,如果有则通知订阅者
  • 订阅者 Watcher,收到属性的变化通知并执行相应的函数,从而更新视图

所以 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 构造函数中的两个参数具体是:

  • target 是用 Proxy 包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler 是一个对象,其声明了代理 target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数

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 即用来拦截某个属性赋值操作的方法,可以接受四个参数:

  • target: 目标值
  • key:目标的 key
  • value:当前需要做改变的值
  • receiver:改变前的原始值

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

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 实现数据劫持的方式。总结一下:

  • 在 ES6 支持,即是 Javascript 标准层面的方式
  • 强大的功能支持,能够更方便支持业务定制,完全可以取代 Object.defineProperty
  • ES6 Reflect 为了操作对象而提供的新 API,直接静态拥有 Proxy 的 13 种方法。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant