专栏首页KrryblogVue3 为何使用 Proxy 实现数据监听
原创

Vue3 为何使用 Proxy 实现数据监听

博客地址:https://ainyi.com/93

vue3 响应式数据放弃了 Object.defineProperty,而使用==Proxy==来代替它

我们知道,在 vue2 中,实现数据监听是使用==Object.defineProperty== --> 实现方法可看:vue 数据双向绑定原理

而这个方法有缺点,并且不能实现数组和对象的部分监听情况;具体也可以看我之前写的一篇博客:关于 Vue 不能 watch 数组 和 对象变化的解决方案

最新的 Proxy,相比 vue2 的 Object.defineProperty,能达到==速度加倍==、==内存减半==的成效。

具体是怎么实现、以及对比旧的实现方法为啥能有速度加倍、内存减半的特性

下面来聊聊

Vue 初始化过程

Vue 的初始化过程,分别有==Observer==、==Compiler==和==Watcher==

当我们 new Vue 的时候,会调用==Observer==,通过 Object.defineProperty 遍历 vue 对象的 data、computed 或者 props(如果是组件的话)的所有属性进行监听。同时通过==Compiler==解析模板指令,解析到属性后就 new 一个==Watcher==并绑定更新函数到 watcher 当中,Observer 和 Compiler 就通过属性来进行关联

WechatIMG198.png

如上,当 Observer 中的 setter 检测到属性值改变的时候,就调用属性对应的所有 watcher 调用更新函数,从而更新到属性对应的 dom

Object.defineProperty

来个简单的 Object.defineProperty 例子

class Observer {
  constructor(data) {
    // 遍历参数 data 的属性,给添加到 this 上
    for(let key of Object.keys(data)) {
      if(typeof data[key] === 'object') {
        data[key] = new Observer(data[key]);
      }
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          console.log('你访问了' + key)
          return data[key]
        },
        set(newVal) {
          console.log('你设置了' + key)
          console.log('新的' + key + '=' + newVal)
          if(newVal === data[key]) {
            return
          }
          data[key] = newVal
        }
      })
    }
  }
}

const obj = {
    name: 'app',
    age: '18',
    a: {
        b: 1,
        c: 2,
    },
}
const app = new Observer(obj);
app.age = 20;
console.log(app.age);
app.newPropKey = '新属性';
console.log(app.newPropKey);

输出如下:

你设置了age
新的age=20
你访问了age
20
新属性

从上面可以知道:

  1. Object.defineProperty 需要遍历所有的属性,这就造成了如果 vue 对象的 data/computed/props 中的数据规模庞大,那么遍历起来就会慢很多
  2. 同理,如果 vue 对象的 data/computed/props 中的数据规模庞大,那么 Object.defineProperty 需要监听所有的属性的变化,那么占用内存就会很大

Proxy

再来看看 Proxy

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

可以理解为在对象之前设置一个”拦截“,当监听的对象被访问的时候,都必须经过这层拦截。可以在这拦截中对原对象处理,返回需要的数据格式

也就是无论访问对象的什么属性,==之前定义的==或是==新增的==属性,都会走到拦截中进行处理。这就解决了之前所无法监听的问题

官方例子:

const p = new Proxy(target, handler)

参数

  • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

来个实际 Proxy 例子

const obj = {
  name: 'krry',
  age: 24,
  others: {
    mobile: 'mi10',
    watch: 'mi4'
  }
}
const p = new Proxy(obj, {
  get(target, key, receiver) {
    console.log('查看的属性为:' + key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log('设置的属性为:' + key);
    console.log('新的属性:' + key, '值为:' + value);
    Reflect.set(target, key, value, receiver);
  }
})
p.age = 22
console.log(p.age)
console.log('--------')
p.single = 'NO'
console.log(p.single)
console.log('--------')
p.others.shoe = 'boost'
console.log(p.others.shoe)

输出如下:

设置的属性为:age
新的属性:age 值为:22
查看的属性为:age
22
--------
设置的属性为:single
新的属性:single 值为:NO
查看的属性为:single
NO
--------
查看的属性为:others
查看的属性为:others
boost

由上可知,新增或编辑属性,并不需要重新添加响应式处理,都能监听的到

因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的

区别

Proxy 和 Object.defineProperty 的使用方法看似很相似,其实 Proxy 是在==更高维度==上去拦截属性的

Object.defineProperty

Vue2 中,对于给定的 data:如 { count: 1 },是需要根据具体的 key 也就是 count,去对 get 和 set 进行拦截,也就是:

Object.defineProperty(data, 'count', {
  get() {},
  set() {},
})

必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力,所以 Vue 初始化的过程中需要遍历 data 来挟持数据变化,造成速度变慢,内存变大的原因

Proxy

而 Vue3 所使用的 Proxy,则是这样拦截的:

new Proxy(data, {
  get(key) { },
  set(key, value) { },
})

可以看到,proxy 不需要关心具体的 key,它去拦截的是 ==修改 data 上的任意 key== 和 ==读取 data 上的任意 key==

所以,不管是已有的 key 还是新增的 key,都会监听到

但是 Proxy 更加强大的地方还在于 Proxy 除了 get 和 set,还可以拦截更多的操作符,具体可看 MDN

兼容性

Proxy 对 IE 不友好,vue3 在检测到使用 IE 的情况下(包括 IE11),会自动降级为 Object.defineProperty 的数据监听系统

博客地址:https://ainyi.com/93

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ES6 中 Promise 详解

    Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的...

    Krry
  • 利用 Blob 处理 node 层返回的二进制文件流字符串并下载文件

    看到标题有点懵逼,哈哈,实际上是后端将文件处理成二进制流,返回到前端,前端处理这个二进制字符串,输出文件或下载

    Krry
  • 页面性能优化

    可以看到,六个绿色条并发请求,四个灰色条等待请求,最下面三个绿色条3.4s后才触发请求

    Krry
  • JavaScript基础之三——基本运算符 原

        大多数语言支持的基本运算符都差别不大。其中最常用的莫属赋值运算符,编程初学者总是会将赋值运算符与相等运算符混淆,需要注意,赋值运算符用于将等号右侧的值赋...

    珲少
  • 前端学习(28)~js学习(六):运算符

    运算符:也叫操作符,是一种符号。通过运算符可以对一个或多个值进行运算,并获取运算结果。

    Vincent-yuan
  • 新手怎么学JS?JavaScript基础入门

    在正式的学习JavaScript之前,我们先来学习一些小工具,帮助我们更好的学习和理解后面的内容。

    Tz一号
  • 你收到的文件被恶意篡改过吗? 摘要算法简介

    想象这样的场景,有客户给你发机密文件。那你怎么确定你收到的文件就是客户发你的,而没有被第三方恶意篡改过呢?

    Joel
  • 怎么用 Redis 快速实现一个延迟队列?

    在后端服务中,经常有这样一种场景,写数据库操作在异步队列中执行,且这个异步队列是多进程运行的,这时如果对同一资源进行写库操作,很有可能产生数据被覆盖等问题,于是...

    用户1278550
  • 2015.12.09 HTML5真题练习

    HTML5学堂:每天一道题,强壮程序员!今日主要涉及12.08题目(原型、原型链、this属性)的解答,以及一道涉及数据类型基本知识的题目。 HTML5真题【2...

    HTML5学堂
  • Android 自定义 view 动画按钮

    昨天偶偶然看见UI 给的一个交互的效果,原图如下 ? 就是下面的loginbutton,于是大概模仿了一下, 并没有做这个UI的全部效果,有兴趣的可以完善后面...

    非著名程序员

扫码关注云+社区

领取腾讯云代金券