持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
上篇文章vue2关于响应式原理源码详解(1)主要讲解vue2响应式的底层原理,通过object.defineProperty对data进行get和set数据劫持,那么具体vue对数组时如何监听的,内部的依赖收集和派发更新又是怎样进行的呢?本文继续从源码一步步带你了解Dep类和Watcher类。
数组的响应式处理
src/core/observer/array.ts
当响应式数据为数组时,则访问 arrayMethods 对象上的七个方法时会被拦截,从而实现数组响应式。
首先备份数组的原型对象为arrayProto,通过继承的方式object.create(arrayProto)创建新的arrayMethods。
如当响应式属性数组访问了push方法,先定义original缓存原生方法,
执行def方法,即object.defindeProperty拦截创建新的arrayMethods.method的访问
先执行原生方法,即push方法,接着判断是否有新插入元素,如果有新插入元素,则对新插入元素进行响应式处理。
需要注意,删除数据的时候,也需要由 dep 通知 watcher 去更新
可以看看def的具体实现,在/src/core/util/lang.ts中。
Dep 依赖管理
我们虽然监听到了数据的变化,但是我们怎么处理通知视图更新呢?dep就是用来帮助我们收集需要将通知到哪里的一个依赖管理。dep负责依赖的存放、添加、删除、通知更新等,相当于是对watcher的管理
每一个dep对应一个obj.key 在读取响应式数据时,负责依赖收集,每个dep依赖的watcher有哪些,在响应式数据更新时,就会通知dep中的哪些watcher去执行update方法。
另外它有一个静态属性target,这是一个全局watcher,也表示同一时间只能存在一个全局的watcher
/src/core/observer/dep.js
在dep中添加watcher:
在dep中移除watcher:
向watcher中添加dep:
通知dep中存储的对应的watcher,执行派发更新操作:
同一时间只有一个观察者使用,赋值观察者
pushTarget是在需要进行依赖收集的时候调用,将当前Dep.target赋值给watcher
popTarget是在依赖收集结束的时候调用,将Dep.target设置为null.
watcher观察者(订阅者)
watcher相当于中介,一个组件一个 watcher(渲染 watcher)或者一个表达式一个 watcher(用户watcher),当数据更新时 watcher 会被触发。
src/core/observer/watcher.ts
get()方法负责监听,执行 this.getter,并重新收集依赖,并触发patch
this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数
addDep完成两件事:添加 dep 给自己(watcher)和添加自己(watcher)到 dep
cleanupDeps() 方法是移除订阅,这里主要做的事为:
- 先遍历上一次添加的 deps,移除 dep中的 Watcher 的订阅
- 然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换
- 最后把 newDepIds 和 newDeps 清空
更新时调用update()方法。这里会维护一个队来存放watcher,并把每次更新的watcher都放到nextTick()里,更新时会遍历队列,排序,触发beforeUpdate,执行run()更新,重置状态,最后执行钩子函数actived和updated
其中,当lazy为true,即懒执行时会将 dirty 置为 true,可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果
当sync为true,即同步执行时在数据更新时该 watcher 就不走异步更新队列,直接执行 this.run
run方法由刷新队列函数 flushSchedulerQueue 调用,主要做的事为:
- 执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)
- 更新旧值为新值
- 执行实例化 watcher 时传递的第三个参数,比如用户 watcher 的回调函数
懒执行的 watcher 会调用该evaluate方法