浅谈vue2中的响应式数据

787 阅读4分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」。


前言

虽然vue3已经出来有一段时间了,网上也有不少的资料文章在讲解、分析vue3的源码。但是这并不影响阅读vue2的源码来学习,这里笔者浅谈一下自己翻阅vue2源码之后的一些理解和记录

先思考以下问题

  1. 初始化数据,如何实现数据响应式
  2. 步骤流程是怎么样的
  3. 响应式数据有什么缺陷

准备工作

首先我们需要到vue的仓库下克隆一份代码:仓库传送门

我克隆的是 2.6.14 版本的

然后本地下载依赖,构建完就可以在本地新建工程进行调试了,这里建议一遍阅读源码一遍调试,因为这样让你对整体流程能有更好的理解

注意:在window下,2.6.14版本的rollup-plugin-alias插件构建后路径有问题,所以我们需要将插件升级一下,笔者是升级到1.4.0版本

流程分析

执行顺序

我们可以在根目录的index.js中看到,入口函数最开始做的时候是将Vue对象传入initGlobalAPI方法中。这里我们先不看initGlobalAPI具体实现了什么,我们来看看Vue对象 (被引用的路径为:./instance/index) 被引用的过程中执行了什么 image.png

打开 ./instance/index 文件 我们可以看到仅仅只是执行了一些方法,当然,这里我们姑且认为不知这些方法是干嘛的。

我们可以看到第一个执行的方法名为:initMixin ,这个方法引用自 ./init,打开文件看看这个方法体内究竟实现了什么。./init 文件如图: image.png

不难看出initMixin中定义了Vue.prototype._init,在_init中 又按顺序执行了 initProxy(vm)、initLifecycle(vm)、initEvents(vm)、initRender(vm)、callHook(vm, 'beforeCreate')、initInjections(vm)、initState(vm)、initProvide(vm)、callHook(vm, 'created')

从函数命名不难看出这个文件就是让vue开始像vue的真正文件,从上面的几个函数名看,可以猜出initState这个函数便为初始化状态的方法,我们进入这个方法内一探究竟。

然后在该文件内又执行了 initProps、initMethods、initData|observe(vm._data)、initComputed、initWatch image.png

执行initData()初始化数据,在该函数内,主要对option.data进行了赋值。其中执行了逻辑包括,判断data是否格式正确,是否与props、methods冲突。前置条件都满足时,则将定义的数据通过 proxy()挂载到vm._data上 (this是vm的实例 所以挂载到vm时候,后续通过this.就可以访问vm所有的属性了) 再通过 observe(data, asRootData:Boolean) 加入依赖收集中,使其成为响应式数据 image.png

observe(data, true) 先判断data是否不为对象和虚拟dom,是的话结束。 否则判断data里是否已有'__ ob__' 如有这个属性,则表示已经是响应式数据,直接赋值给ob对象。如果没有'__ ob__'属性,则ob= new Observer(value) 即新建一个Observer实例。最后如果是根节点的话ob.vmCount++ 返回ob对象 image.png

new Observer()中,实现的是: 将data对象里面的属性设置为响应式的。先给data对象添加'__ ob__'属性,然后判断是否为数组,如果是数组的话,判断对象是否有__proto__ 无则添加数组的默认方法到__proto__,有的话直接将数组方法赋值到__proto__属性上,然后再遍历数组,再逐个调用observe()方法。如果不为数组,遍历对象属性,通过defineReactive(obj, keys[i]) 将数据设置成响应式的 image.png

defineReactive():在每个数据中,都生成Dep实例,并判断该数据是否有子项。然后再通过 Object.defineProperty重写get、set,实现数据的劫持。由于在实现数据劫持的时候,对于数组的处理,是直接将整个数组({aryList: [1,2,3]})丢进检查,因此在业务层,我们通过下标修改数组的值,或者新增删除项的时候,并不能触发数据监测。

get:其中实现了:在每个数据中执行dep.depend()方法添加依赖,执行过程中会生成id,sub队列。依赖收集以后,每个属性都会有一个Dep来保存所有Watcher对象。向依赖收集器添加收集登记?如果有子数据,且子数据为数组则深度遍历子数据并添加dep

set:这里是用于已经被收集的数据,在更新的时候触发并同步监测。更新数据的值,然后通过dep.notify()通知订阅者watcher进行update()