vue源码系列05_发布订阅模式

vue源码系列05_发布订阅模式


所谓依赖收集,就是在每个数据渲染更新的时候,给每个数据添加一个watcher监听类,当该数据发生变化时,用一个dep队列来实现收集这些watcher,最后逐一触发watcher中的update进行渲染
这是网友的一张图

在这里插入图片描述
概要:

  1. 在数据初始化的时候,会给每个属性配置一个dep类监视并绑定给同一个watcher (该watcher是全局watcher)
  2. 当第一次页面渲染的时候,全局watcher会默认执行update方法进行页面更新
  3. dep内部有一个栈专门保存watcher,当dep负责的那个属性数据发生改变时,会依次触发栈内所有watcher的update方法进行页面渲染。(发布)
  4. 当然watcher(视图)可以包含多个dep(订阅),当它所负责dep中有一个数据发生改变了,那个dep会触发 notify() 方法使得该 watcher 触发 update() 进行页面渲染

可以简单地理解为:

  • 一个视图(watcher)可以包含多个dep(数据),当它所包含的dep(数据)发生改变时(数据劫持),该视图就会自动触发更新,而其他视图(watcher)如果没有包含发生数据变化的那个dep,就不会发生视图更新从而实现局部更新
  • 一个数据(dep)也可以同时运用在多个视图中(watcher),当该dep所监视的数据发生改变时,它所保存的视图(watcher)将会按顺序发生视图更新,并且不影响其他watcher(发布)
    以下为依赖收集的流程

dep.js

主要的操作:

  1. 给不同的dep赋一个id
  2. 创建sub数组(实际上是队列)
  3. 创建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

  1. 在 get() 方法中,在更新方法之前压入栈,更新之后退出栈
get() {
    pushTarget(this)
    let value = this.getter()
    popTarget()
    return value // 返回老值
}
  1. 添加update更新方法
  2. 给每个watcher添加 dep数组与depId的Set集合
  3. 添加 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) // 订阅
    }
}
  1. 实现异步更新,这一步是为了防止同一个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()
        }
    })
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章