Vue2.0和3.0的响应式原理解析


本文参考于:

https://www.jianshu.com/p/d137fbdc06ff

https://www.cnblogs.com/jiajialove/p/11323105.html(这个说的比较好)

https://segmentfault.com/a/1190000019700618?utm_source=sf-related(这个很详细)

https://blog.csdn.net/xiaoxiaole0313/article/details/104624019/(图解Vue3.0响应式)

Vue2.0响应式

总结:

总结:Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 getset 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。

响应式架构图

架构图

虚拟DOM (Virtaul DOM): 用 js 对象模拟的,保存当前视图内所有 DOM 节点对象基本描述属性和节点间关系的树结构。用 js 对象,描述每个节点,及其父子关系,形成虚拟 DOM 对象树结构。

Vue2.0响应式核心步骤

1. observe(data)

observe(data)

如上图所示,observe函数所做的工作就是遍历data的每一个属性,对这些属性依次做响应式处理

2.defineProperty(data,key,handler)

defineProperty(data,key,handler)

而所谓的响应式处理,则是拦截属性的get、set方法做订阅-发布处理。订阅-发布处理,就是在属性被调用的使用,触发get代理函数,订阅调用该属性的组件(将组件存放到一个订阅者数组中进行保存,这里的Dep.target暂时可以理解为一个全局变量,代表着的是当前正在渲染的Vue组件)。 而在属性被修改时,触发set代理函数,在set代理函数里,通知订阅者数组里面的每一个订阅者(组件)进行视图更新。

3.Dep

上文中提到的Dep,是一个依赖类,将它理解成一个订阅者。当对一个属性进行响应式处理的时候,就会实例化一个Dep实例,并将用到这个属性的组件全部存放在subs数组里。当属性被修改时,则通知subs数组里的所有组件进行更新。

Dep

4. Watcher

上面我们说到,dep.subs里面存的是用到该属性的组件,这个说法实际上不完全对。准确来说,dep.subs里面存的是用到该属性的组件所对应的一个Watcher实例。什么是Watcher?每一个组件在渲染之前,会对其自身的template模版进行解析编译,生成一个render函数(用于创建虚拟dom)。然后,会实例化一个Watcher对象,在实例化的过程中,会将之前生成的render函数作为参数传入进去执行。而在执行render的过程中,势必会触发相应属性的get函数,将这个watcher实例添加到相应属性的订阅者数组subs当中。等这些属性进行被修改时,就会通知这个watcher,再次调用这个watcher里面的render函数,进行虚拟dom的diff和更新。

Vue2.0的不足

1.动态添加响应式属性必须用Vue.set

2.直接操作数组索引无法触发视图更新

3.数据的响应式处理和视图未完全解耦

Vue3.0响应式

Vue3.0响应式原理框架图

img

Vue3.0响应式的核心步骤

1.reactive(data)

reactive(data)

2.Proxy(data,handler)

Proxy(data,handler)

从上图代码可以看出,Vue3.0响应式相对于Vue2.0的最大区别在于用到了es6中的方法Proxy。这个方法不需要循环遍历data的每个属性,对每个属性都做一遍响应式处理。而是直接代理了整个data对象,拦截这个对象所包含的所有属性的get、set方法。这么做的好处:

1.就是在我们动态为data添加一个属性时,不用做任何处理,这个属性就已经是响应式的了。

2.数组的任何操作也都可以触发响应。

3.effect(fn)

上面的设计,其实已经解决了Vue2.0的前两个不足。而为了解决第三个不足, Vue3.0提出了effect函数,其用法如下:

img

当我们调用effect执行某个函数时,首先会执行一次这个函数。之后每次obj.name修改时,执行一次该函数。那么effect是怎么实现这个功能的?

img

我们来看这个effect函数做了什么?简单来说,effect会先将传入的回调函数保存起来,然后第一次执行cb(),在执行的过程中,触发了obj.name属性的get代理函数,在代理函数里面做订阅track(target), 而track函数则会将存在全局数组中的cb函数取出来,保存在map中,类似于map.set(‘name’,cb)。之后,在name属性被修改的时候,会触发name属性的set代理函数,在代理函数里面执行trigger。最后,trigger会将之前存在map里的订阅着取出来执行,map.get(‘name’)()


评论
  目录