vue源码系列05_发布订阅模式
所谓依赖收集,就是在每个数据渲染更新的时候,给每个数据添加一个watcher监听类,当该数据发生变化时,用一个dep队列来实现收集这些watcher,最后逐一触发watcher中的update进行渲染
这是网友的一张图
概要:
- 在数据初始化的时候,会给每个属性配置一个dep类监视并绑定给同一个watcher (该watcher是全局watcher)
- 当第一次页面渲染的时候,全局watcher会默认执行update方法进行页面更新
- dep内部有一个栈专门保存watcher,当dep负责的那个属性数据发生改变时,会依次触发栈内所有watcher的update方法进行页面渲染。(发布)
- 当然watcher(视图)可以包含多个dep(订阅),当它所负责dep中有一个数据发生改变了,那个dep会触发 notify() 方法使得该 watcher 触发 update() 进行页面渲染
可以简单地理解为:
- 一个视图(watcher)可以包含多个dep(数据),当它所包含的dep(数据)发生改变时(数据劫持),该视图就会自动触发更新,而其他视图(watcher)如果没有包含发生数据变化的那个dep,就不会发生视图更新从而实现局部更新
- 一个数据(dep)也可以同时运用在多个视图中(watcher),当该dep所监视的数据发生改变时,它所保存的视图(watcher)将会按顺序发生视图更新,并且不影响其他watcher(发布)
以下为依赖收集的流程
dep.js
主要的操作:
- 给不同的dep赋一个id
- 创建sub数组(实际上是队列)
- 创建stack栈,创建保存watcher与取出watcher的方法,并且让 Dep.target 得到当前 watcher (后面有用到)
主要的几个方法
- addSub() 订阅
向 sub 数组添加一个watcher - notify() 发布
遍历 sub 数组,让每个watcher执行 update() 方法 - depend() 存watcher
调用watcher.addDep(this),让watcher记录Dep
let id = 0
class Dep {
constructor(){
this.id = id++
this.subs = []
}
addSub(watcher){ //订阅
this.subs.push(watcher)
}
notify(){ //发布
this.subs.forEach(watcher =>{
watcher.update()
})
}
depend(){
if (Dep.target){
Dep.target.addDep(this)
}
}
}
// 保存当前watcher
let stack = []
export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}
export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
export default Dep
watcher.js
- 在 get() 方法中,在更新方法之前压入栈,更新之后退出栈
get() {
pushTarget(this)
let value = this.getter()
popTarget()
return value // 返回老值
}
- 添加update更新方法
- 给每个watcher添加 dep数组与depId的Set集合
- 添加 addDep() 方法 去重
addDep(dep) {
let id = dep.id
// 当该watcher没有相同的 dep
if (!this.depsId.has(id)) {
this.depsId.add(id)
this.deps.push(dep) // 记录当前dep
dep.addSub(this) // 订阅
}
}
- 实现异步更新,这一步是为了防止同一个watcher连续多次刷新,通过这一步我们可以实现只运行最后一步,提高性能(批量更新)
let has = {}
let queue = [];
function flusqueue() {
queue.forEach(watcher => watcher.run())
has = {};
queue = []
}
function queueWatcher(watcher) {
let id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
}
nextTick(flusqueue)
}
// 异步执行
let callbacks = [];
function flushCallbacks() {
callbacks.forEach(cb => cb())
}
function nextTick(flusqueue) {
callbacks.push(flusqueue)
let asyncFn = () => {
flushCallbacks()
}
if (Promise) {
Promise.resolve().then(asyncFn)
}
setTimeout(asyncFn, 0)
}
defineReactive()
在每个数据添加响应式的时候,我们给它们添加一个dep,当属性设置的时候,实现更新 dep.notify()
export function defineReactive(data, key, value) {
let childOb = observe(value) // 递归,对data中的对象进行响应式操作,递归实现深度检测
let dep = new Dep()
Object.defineProperty(data, key, {
get() { // 获取值的时候做一些操作
if(Dep.target){
// wacher 里面记录dep 也在dep里面记录watcher
dep.depend()
// dep.addSub(watcher)
if(childOb){
childOb.dep.depend() // 数组收集当前渲染的watcher
dependArray(value) // 收集儿子的依赖
}
}
return value;
},
set(newValue) { // 也可以做一些操作
console.log("设置属性",newValue)
if (newValue == value) return;
observe(newValue); //继续劫持用户设置的值,因为用户设置的值有可能是一个对象
value = newValue;
// 当属性设置的时候,实现更新
dep.notify()
}
})
}