初探Vue3.0的响应式原理

752 阅读5分钟

上期简单记录了下Vue3.0给我们带了哪些变化,本来想打算研究下周边配套插件,结果发现在目前的版本中好像还没法使用VuexVue-Router,至少目前我没找到相关资料。那本期就来探索一下Vue3.0响应式原理

之前在尤大提出Vue3.0响应式要使用Proxy来重写的时候我就去看过Proxy的相关介绍,但是在平时项目中基本是用不到这个的,所以理解起来肯定有点儿吃力(明明是自己太菜了好吗?),正好这段时间项目时间不紧张,抽空看了下Vue3.0的响应式原理,下面简单记录一下。

我们都知道Vue2.x的响应式是基于Object.defineProperty来实现的,其缺点我们应该也知道,它不能对对象的属性的增删操作做出处理,同时也不能监听数组的下标的变化,我们先来看看2.x中响应式的核心部分:

2.x响应式核心部分
位置:vue/src/core/observer/index.js

2.x响应式核心部分 可以大致看到2.x的响应式系统是在get时进行依赖收集,在set时进行消息通知,也就是说在Vue组件初始化时,Vue会对data中的每一个属性进行如上操作,关于Vue2.x源码目前我还只能看懂一些,代码中一层套一层,跟俄罗斯套娃一样,看不了多少就被绕晕了。

相较于2.x,3.0的源码看上去就友好很多,而且3.0的响应式系统是被独立出来的,可以与其他任何框架结合使用,因此代码之间的嵌的就不是很复杂,也不用在多个文件之间来回切换。接下来我们来看看3.0的响应式原理。

在看3.0的响应式原理前,我想我们有必要先了解一下ProxyReflect,只有了解了ProxyReflect后我们才能更好的理解3.0的响应式原理。

Proxy

ES6中是这样描述Proxy的:Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(metaprogramming),即对编程语言进行编程。Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

什么意思?就是说我们能够在对一个对象进行操作前能够对该行为进行修改,比如getset,这个不是跟Object.defineProperty一样的吗?并不是,Object.defineProperty是对对象属性的行为做修改,而Proxy代理的是整个对象,这也是2.0不能检测对象属性的增删的原因,当然我们可以通过其他方法为对象添加属性。Proxy厉害就厉害在这里,当然Proxy支持的不止这两个行为,还有其他11种行为,详细的请参见:ECMAScript 6 入门

Reflect

Reflect在ES6中的概述是这样的:Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新 API。

emmmm,啥子意思?看不懂啊,看了下例子,似乎明白了一些,Reflect中的方法和Proxy中的方法是一一对应的,也就是说Reflect同样也有13种行为,对于getset会返回默认的行为,看下面例子: Reflect set 示例

当我们注释到上面的 return Reflect.set(target,key,value)时,控制会打印出:阿强强 Change By Proxy,去掉注释后,控制台会打印出:阿强强,我们明明已经通过target[key] = value+' Change By Proxy'修改了name属性的值,但是使用Reflect.set(target,key,value)后,会返回修改前的值,同样get也是如此,大家可以自行测试。

简单了解了ProxyReflect后,接下来我们来看看3.0的响应式原理。

3.0响应式核心部分
位置:@vue/reactivity/dist/reactivity.global.js

3.0响应式核心部分

这里是我们使用reactive函数创建响应式数据时需要调用的函数,这里还不是最为关键地方,最关键的地方应该handler部分。接下来我们看看 各个handler

handler一共有四个,分别为:

Handler类型
mutableHandlers可读写
shallowReactiveHandlers可读写
readonlyHandlers只读
shallowReadonlyHandlers只读
我们从名字中就能看出几个handler的细微区别,具体有哪些差别大家可查看源码:@vue\reactivity\dist\reactivity.global.js,今天简单查看一下mutableHandlers这个handler中的getset

Handler 中的 setter 部分
位置:@vue\reactivity\dist\reactivity.global.js

createSetter 这里可以看出Vue3.0默认是会对增加对象属性做出响应,这里的trgger暂时还没来的及看,猜测应该是进行依赖收集。

再看看getter的源码:

Handler 中的 getter 部分
位置:@vue/reactivity/dist/reactivity.global.js

createGetter

在我看到getter之前我一直有个疑问: vue是在什么时候对整对象进行依赖收集的,我们知道vue2.x在应用初始化时对data中的字段递归的进行依赖收集,但是当我在getter中的看到:

        return shared.isObject(res)
            ? isReadonly
                ? // need to lazy access readonly and reactive here to avoid
                    // circular dependency
                    readonly(res)
                : reactive(res)
            : res;
            
            

vue3getter中对属性值为Object的类型又进行了一次代理,大胆猜测一下:难道vue3依赖收集是实时进行的?好了本期先就探索到这里,文中有不当地方还请各位指正,不胜感激!

转载或摘抄须注明出处。