通过Proxy和Reflect实现vue的响应式原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过Proxy和Reflect实现vue的响应式原理相关的知识,希望对你有一定的参考价值。

参考技术A Proxy是ES6中增加的类,表示代理。
如果我们想要监听对象的操作过程,可以先创建一个代理对象,之后所有对于对象的操作,都由代理对象来完成,代理对象可以监听到我们对于原对象进行了哪些操作。

Proxy是一个类,通过new关键字创建对象,传入原对象和处理监听的捕获器

对代理所作的操作,同样会作用于原对象

捕获器就是用来监听对于对象操作的方法

以上get、set是捕获器中常用的两种,分别用于对象数据的“读取”操作和“设置”操作

Proxy里对应捕获器与普通对象的操作和定义是一一对应的

将这些捕获器以及对应的对象操作写在了以下示例中

通过捕获器去监听对象的修改、查询、删除等操作

以上捕获器中只有apply和constructor是属于函数对象的

apply方法执行apply捕获器,new操作执行constructor捕获器

Reflect也是ES6新增的一个API,表示反射。它是一个对象,提供了很多操作对象的方法,类似于Object中的方法,比如 Reflect.getPrototypeOf 和 Object.getPrototypeOf。

早期操作对象的方法都是定义在Object上,但Object作为构造函数,直接放在它身上并不合适,所以新增Reflect对象来统一操作,并且转换了对象中in、delete这样的操作符

Reflect中的方法与Proxy中是一一对应的

将之前通过Proxy设置代理的对象操作全都变为Reflect

实现的效果是完全一致的

Reflect中在进行get/set捕获器操作的时候,还有一个入参是receiver,指的是代理对象,用于改变this指向

(1) 如果没有receiver,那么当修改name属性时,objProxy先执行key为name时的get操作
(2) 然后代理到obj里的get方法,读取this的_name属性,此时的this是obj,会直接修改
obj._name,不会再经过objProxy
(3) 增加了receiver之后,执行obj的get方法,读取this的_name属性,此时this是proxy
对象,所以会再次到get的捕获器中

set操作同理

用于改变this的指向

此时创建的student对象虽然拥有Student的属性和方法,但是它的this指向Person

通过以下步骤一步步实现响应式

此时通过响应式函数 watchFn 将所有需要执行的函数收集进了数组中,然后当变量的值发生变化时,手动遍历执行所有的函数

以上只有一个数组来收集对象的执行函数,真实情况下,不止一个对象需要对操作状态进行监听,需要监听多个对象就可以使用类。

将收集操作和依次执行函数的方法都定义在类中

以上仍然是我们自己手动调用执行函数的方法,以下自动监听

此时的问题是,修改了对象的任一属性,所有的函数都会调用,没有按照一一对应的关系来保存对象的属性和对应的函数

此时proxy对应的depend是没有值的,所以此时没有任何打印的数据

此时已经能够根据属性值的变化而执行对应的函数了,但同一个函数会执行两次

此时已实现vue3的响应式~

vue2的实现就是将Proxy和Reflect替换成了Object.defineProperty和Object本身的一些方法

和上面实现的效果是一致的

以上就是通过Proxy和Reflect实现vue的响应式原理,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~

vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy

vue3响应式原理

之前写过一篇文章谈论 vue2.x响应式原理,但因为 vue3 也来了,紧跟着 vue3 的步伐,周一开始学起了 vue3 的响应式原理。

大家应该都听过, vue3proxy 来解决响应式原理,同时它解决了 vue2Object.definePropery 存在的一些问题,但同时也带来了一些问题。

在下面的这篇文章中,将讲解关于 vue3proxy 如何实现响应式,以及带来的一些问题。一起来学习吧💯

一、🟩回顾Object.defineProperty

这里需要大家对 ObjectProperty 的知识点有一个预先了解,如有需要了解可点击文章进行查看~

现在,我们来回顾下 Object.defineProperty缺点:

  • 深度监听时需要一次性递归;
  • 无法监听新增属性/删除属性(需要配合 Vue.setVue.delete 使用);
  • 无法原生监听数组,需要特殊处理。

带着 Object.defineProperty 的这几个缺点,接下来我们开始进入 Proxy 的世界。

二、🟨Proxy基本使用

下面用一段代码来演示 Proxy 的基本使用。具体代码如下:

const data = {
	name: 'monday',
    age:18
}
//const data = ['a', 'b', 'c']

const proxyData = new Proxy(data, {
    get(target, key, receiver){
        //只处理本身(非原型的)属性
        const ownKeys = Reflect.ownKeys(target)
        if(oenKeys.includes(key)){
            console.log('get', key) //监听
        }
        const result = Reflect.get(target, key, receiver)
        return result // 返回结果
    },
    set(target, key, val, receiver){
        //重复的数据,不处理
        if(val === target[key]){
            return true
        }
        
        const result = Reflect.set(target, key, val, receiver)
        console.log('set', key, val)
        //console.log('result', result) //true
        return result // 是否设置成功
    },
    deleteProperty(target, key){
        const result = Reflect.deleteProperty(target, key)
        console.log('delete property', key)
        return result // 是否删除成功
    }
})

通过以上代码可得,我们先定义一个对象字面量的 data ,之后在作为 Proxy 实例化的参数进行传递。且 proxyData 实现了 getsetdeleteProperty 的方法,可以对数据进行增删改操作。

三、🟦学习Proxy语法:Reflect

我们再来认识 Proxy 的一个好朋友,Reflect

Reflect 对象有着和 Proxy 一一对应的能力,Reflect对象一共有 13 个静态方法,这也就是我们平常所听到的 proxy 有多达13种拦截行为,而 Reflect 的这13种静态方法匹配的就是 Proxy13种拦截行为

静态方法列表
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.has(obj, name)
Reflect.deleteProperty(obj, name)
Reflect.construct(target, args)
Reflect.getPrototypeOf(obj)
Reflect.setPrototypeOf(obj, newProto)
Reflect.apply(func, thisArg, args)
Reflect.defineProperty(target, propertyKey, attribute)
Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.ownKeys(target)

Reflect 的出现是为了替换掉 Object 上的工具函数,这里不做具体介绍,详情可查看文档

四、🟧Vue3如何用Proxy实现响应式

1、实现响应式

下面来实现Proxy的响应式。附上代码:

// 创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        // 不是对象或数组,则返回
        return target
    }

    // 代理配置
    const proxyConf = {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('get', key) // 监听
            }
    
            const result = Reflect.get(target, key, receiver)
        
            // 深度监听
            return reactive(result)
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true
            }
    
            const ownKeys = Reflect.ownKeys(target)
            // 判断是已有属性还是新增属性
            if (ownKeys.includes(key)) {
                console.log('已有的 key', key)
            } else {
                console.log('新增的 key', key)
            } 

            const result = Reflect.set(target, key, val, receiver)
            console.log('set', key, val)
            // console.log('result', result) // true
            return result // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key)
            console.log('delete property', key)
            // console.log('result', result) // true
            return result // 是否删除成功
        }
    }

    // 生成代理对象
    const observed = new Proxy(target, proxyConf)
    return observed
}

// 测试数据
const data = {
    name: 'monday',
    age: 18,
    info: {
        city: 'FuZhou',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
}

const proxyData = reactive(data)

我们在控制台来验证数据:

proxy

从上图中可以看到,用 proxy 来实现响应式,如果遇到需要深度递归的数组时,它不会像 defineProperty 那样深度递归,它会在什么时候 get ,什么时候再深度递归。本质上来讲就是,你获取到哪一层,那一层才会触发响应式你获取不到的深层,它就不会触发响应式。且从代码中我们可以了解到, Proxy 在修改属性时,如果数据是重复的,则不进行处理。如果数据不重复,再进行处理。这样一来,就极大程度上提高了软件的性能

2、Proxy总结

现在来对上述Proxy的内容做一个总结:

(1)深度监听,性能更好

defineProperty 是一次性递归完成;而 Proxy 是什么时候 get ,什么时候再深度递归

(2)可监听 新增/删除 属性

vue2 中, defineProperty无法新增/删除属性的,需要配合 Vue.setVue.delete 来使用,而在 Vue3 中, Proxy 可以新增和删除属性,无需进行特殊处理。

(3)可监听数组变化

vue2 中,监听数组变化是需要进行特殊处理,且只能一次性深度递归完成。而在 vue3 中,可以监听数组变化,并且是什么时候get什么时候再递归,获取不到的深层,不会触发响应式。

3、两者对比

讲到这里,我们再把 vue2 中的 Object.definePropertyvue3 中的 Proxy 做一个对比:

  • Proxy 能良好的规避 Object.defineProperty 的问题;
  • Proxy 无法兼容所有浏览器(如 IE11 ),且无法 polyfill

五、🟪结束语

从某种程度上来说, vue3Proxy 确实带来了一些好处,但同时也带来了一些问题。正因为如此, vue2Object.defineProperty 还会存在很长一段时间。所以,新技术的使用总会经过一个从试用阶段到稳定阶段的过程。

关于vue3的响应式原理讲到这里就结束啦!如有疑问或文章有误欢迎评论区留言或私信交流~

  • 关注公众号 星期一研究室 ,第一时间关注技术干货,更多有趣的专栏待你解锁~
  • 如果这篇文章对你有用,记得 一键三连 再走哦!
  • 我们下期见!🥂🥂🥂

以上是关于通过Proxy和Reflect实现vue的响应式原理的主要内容,如果未能解决你的问题,请参考以下文章

敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()

敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()

敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()

vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy

vue2数据响应式原理

Vue3.0源码系列响应式原理 - Reactivity