JavaScript 之数据劫持

发布于 2022-10-09 22:57:04 字数 2984 浏览 115 评论 0

每当收到候选人简历中有 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 种方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

文章
评论
27 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文