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


    前言

    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就通过属性来进行关联。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctcznHpj-1658150409130)(../imgs/watch.jpeg)]
    如上,当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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    输出如下:

    在这里插入图片描述
    从上面可以知道:

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

    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('查看的属性');
            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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    输出如下:

    在这里插入图片描述
    由上可知,新增或编辑属性,并不需要重新添加响应式处理,都能监听的到。因为 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() {},})
    
    • 1

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

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

    可以看到,proxy不需要关心具体的key,它去拦截的是修改data上的任意key和读取data上的任意key。
    所以,不管是已有的key还是新增的key,都会监听到。但是Proxy更加强大的地方还在于Proxy除了get和set,还可以拦截更多的操作符,具体可看MDN。

    兼容性

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

    Vue2.0 不能检测数组和对象的解决方案?

    关于这个问题,我们可以拆分开来讨论,同时针对vue能够监听的场景和无法监听的场景进行讨论,并给出对应的解决方案:

    监听数组的变化:

    1)vue 能够监听数组变化的场景

    1. 通过赋值的形式改变正在被监听的数组;
    2. 通过splice(index, num, val) 的形式改变正在被监听的数组;
    3. 通过数组的push的形式改变正在被监听的数组;

    2)vue 无法监听数组变化的场景

    1. 通过数组索引改变数组元素的值;
    2. 改变数组的长度;

    3)vue 无法监听数组变化的解决方案

    1. this.$set(arr, index, newVal);
    2. 通过splice(index,num,val);
    3. 使用临时变量作为中转,重新赋值数组;

    监听对象的变化:

    1)vue 能够监听对象变化的场景

    1. 通过直接赋值的场景;
      e.g:watchObj = {name:“zyk”}

    2)vue 无法监听对象变化的场景

    1. 对象的增加、删除、修改无法被vue监听到

    3)vue 无法监听对象变化的解决方案

    1. 使用this. s e t ( o b j e c t , k e y , v a l u e ) ( v u e 无法监听 t h i s . set(object, key, value)(vue无法监听this. set(object,key,value)vue无法监听this.set修改原有属性)
    2. 使用 Object.assign(),直接赋值的原理;(深拷贝,推荐使用)。

    关于 Object.assign() — MDN 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
    其实使用 Object.assign() 也是基于深拷贝的实现原理,如果大家对深拷贝不太清楚,请移步至:浅拷贝 VS 深拷贝,并且手写一个深拷贝(深克隆)

  • 相关阅读:
    Java回顾-Collection-Set-HashSet/LinkedHashSet/TreeSet的对比
    基于java的医院微信小程序
    39. 组合总和
    PDF转CAD怎么弄?推荐两个转换方法
    字符函数和字符串函数(1)
    免费屏幕录像机
    Docker通信全视角:原理、实践与技术洞察
    vue antv X6 ER图
    【力扣刷题】Day31——DP专题
    【openEuler系列】配置本地yum源
  • 原文地址:https://blog.csdn.net/Gary_888/article/details/125860568