vue2关于响应式原理源码详解(2)- Dep、Watcher

139 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

上篇文章vue2关于响应式原理源码详解(1)主要讲解vue2响应式的底层原理,通过object.defineProperty对data进行get和set数据劫持,那么具体vue对数组时如何监听的,内部的依赖收集和派发更新又是怎样进行的呢?本文继续从源码一步步带你了解Dep类和Watcher类。

数组的响应式处理

src/core/observer/array.ts

image.png

当响应式数据为数组时,则访问 arrayMethods 对象上的七个方法时会被拦截,从而实现数组响应式。

首先备份数组的原型对象为arrayProto,通过继承的方式object.create(arrayProto)创建新的arrayMethods。

如当响应式属性数组访问了push方法,先定义original缓存原生方法,

执行def方法,即object.defindeProperty拦截创建新的arrayMethods.method的访问

先执行原生方法,即push方法,接着判断是否有新插入元素,如果有新插入元素,则对新插入元素进行响应式处理。

需要注意,删除数据的时候,也需要由 dep 通知 watcher 去更新

可以看看def的具体实现,在/src/core/util/lang.ts中。

image.png

Dep 依赖管理

我们虽然监听到了数据的变化,但是我们怎么处理通知视图更新呢?dep就是用来帮助我们收集需要将通知到哪里的一个依赖管理。dep负责依赖的存放、添加、删除、通知更新等,相当于是对watcher的管理

每一个dep对应一个obj.key 在读取响应式数据时,负责依赖收集,每个dep依赖的watcher有哪些,在响应式数据更新时,就会通知dep中的哪些watcher去执行update方法。

另外它有一个静态属性target,这是一个全局watcher,也表示同一时间只能存在一个全局的watcher

/src/core/observer/dep.js

image.png

在dep中添加watcher:

image.png

在dep中移除watcher:

image.png

向watcher中添加dep:

image.png

通知dep中存储的对应的watcher,执行派发更新操作:

image.png

同一时间只有一个观察者使用,赋值观察者

pushTarget是在需要进行依赖收集的时候调用,将当前Dep.target赋值给watcher

popTarget是在依赖收集结束的时候调用,将Dep.target设置为null.

image.png

watcher观察者(订阅者)

watcher相当于中介,一个组件一个 watcher(渲染 watcher)或者一个表达式一个 watcher(用户watcher),当数据更新时 watcher 会被触发。

src/core/observer/watcher.ts

image.png

image.png

get()方法负责监听,执行 this.getter,并重新收集依赖,并触发patch

this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数

image.png

addDep完成两件事:添加 dep 给自己(watcher)和添加自己(watcher)到 dep

image.png

cleanupDeps() 方法是移除订阅,这里主要做的事为:

  • 先遍历上一次添加的 deps,移除 dep中的 Watcher 的订阅
  • 然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换
  • 最后把 newDepIds 和 newDeps 清空

image.png

更新时调用update()方法。这里会维护一个队来存放watcher,并把每次更新的watcher都放到nextTick()里,更新时会遍历队列,排序,触发beforeUpdate,执行run()更新,重置状态,最后执行钩子函数actived和updated

其中,当lazy为true,即懒执行时会将 dirty 置为 true,可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果

当sync为true,即同步执行时在数据更新时该 watcher 就不走异步更新队列,直接执行 this.run

image.png

run方法由刷新队列函数 flushSchedulerQueue 调用,主要做的事为:

  • 执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)
  • 更新旧值为新值
  • 执行实例化 watcher 时传递的第三个参数,比如用户 watcher 的回调函数

image.png

懒执行的 watcher 会调用该evaluate方法

image.png

image.png