MVVM原理(4):观察者Watcher 依赖收集器Dep

一、Dep

  • Dep的作用是收集观察者以及当数据发生变动时通知观察者去更新
  • 每一个属性都有自身的dep,接着添加watcher,在每次数据变动时(即set),通知自身的dep,dep通知其中watcher去完成视图更新
class Dep {
    constructor () {
        this.subs = []
    }
    // 收集观察者
    addSub (watcher) {
        this.subs.push(watcher)
    }
    // 通知观察者去更新
    notify () {
        this.subs.forEach(w => w.update())
    }
}

二、Watcher

  • Watcher观察者主要作用是接收自身Dep的通知后执行update()函数去更新视图
class Watcher {
    constructor (vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        // 先把旧值保存起来
        this.oldVal = this.getOldVal()
    }
    getOldVal () {
        Dep.target = this  // 在定义watcher时在Dep类上增加属性target指向自身watcher
        const oldVal = compileUtil.getVal(this.expr, this.vm)  // 获取数据时就在属性自身的dep上把该watcher添加上去
        Dep.target = null  // 不设置为null的话每次获取数据就会一直往对应的dep中加入watcher(这时的watcher看此时Dep的target的指向),数据更改后就会执行该dep中的所有watcher的update()
        return oldVal
    }
    update () {
        const newVal = compileUtil.getVal(this.expr, this.vm)
        if(newVal !== this.oldVal) {  // 值改变才去更新
            this.cb(newVal)   // 调用cb函数处理视图
            this.oldVal = newVal   // 把watcher的旧值改为新值
        }
    }
}

三、Observer和Dep的关联

  • 顺序是这样的,先new Vue()然后new Observer()然后new Compile()
  • new Observer()时创建属性各自的dep
  • new Compile()new Watcher(),执行new Watcher()时:会先绑定Dep类上的target为这个new Watcher(),接着执行,需要获取数据getOldVal(),就会执行这里的get(),这样子就顺利把每个属性的watcher添加到各自的dep中了
  • 在数据发生变动时,dep.notify()即可
class Observer {
	...
	defineReactive (obj, key, value) {
        this.observe(value)  // 如果是对象的话就会继续递归监听
        const dep = new Dep()  // 定义该属性自身的dep
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get () {
                Dep.target && dep.addSub(Dep.target)   // 顺序是这样的,先new Vue()然后new Observer()然后new Compile()
                // 在new Compile()中new Watcher(),执行new Watcher()时:会先绑定Dep类上的target为这个new Watcher(),接着执行,需要获取数据getOldVal(),就会执行这里的get(),这样子就顺利把每个属性的watcher添加到各自的dep中了
                return value
            },
            set: (newVal) => {   // 使用普通函数的话this指向obj
                if(newVal !== value) {
                    this.observe(newVal)   // 对新值也要进行监听
                    value = newVal
                }
                dep.notify()  // 当数据变动时通知自身的dep
            }
        })
    }
}

四、创建watcher的时机

  • 在初始化new Compile()的过程中
  • new Watcher()中把watcher添加到属性自身的dep
  • 仅当初始化完成后,在用户交互过程中数据变动才会触发dep去执行watcher.update()

五、数据变动修改视图

修改compileUtil

  • v-html中增加watcher
const compileUtil = {
	html (node, expr, vm) {
        const value = this.getVal(expr, vm)
        new Watcher(vm, expr, (newVal) => {  // 当数据变化时执行该回调函数修改对应的视图
            this.updater.htmlUpdater(node, newVal)
        })
        this.updater.htmlUpdater(node, value)
    },
}
  • v-model中增加watcher
const compileUtil = {
	model (node, expr, vm) {
        const value = this.getVal(expr, vm)
        // 绑定更新函数,数据=>视图
        new Watcher(vm, expr, (newVal) => {  // 当数据变化时执行该回调函数修改对应的视图
            this.updater.modelUpdater(node, newVal)
        })
        this.updater.modelUpdater(node, value)
    },
}
  • v-text以及普通文本的修改:
const compileUtil = {
    text (node, expr, vm) {
        let value
        let reg = /\{\{(.+?)\}\}/g
        if(reg.test(expr)) {  // 处理文本
            value = expr.replace(reg, (...args) => {  // 普通文本中可能有多个{{}}插值,因此要匹配出来逐一增加`watcher`
                new Watcher(vm, args[1], () => {
                    this.updater.textUpdater(node, this.getContentVal(expr, vm))
                })
                return this.getVal(args[1], vm)
            })
        } else {
            value = this.getVal(expr, vm)  // 处理v-text
            new Watcher(vm, expr, (newVal) => {
                this.updater.textUpdater(node, newVal)
            })  
        }
        this.updater.textUpdater(node, value)
    },
    ...
    getContentVal (expr, vm) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(args[1], vm)
        })
    },
}

至此,我们就实现了数据=>视图的功能了

上一篇:shell变量的运算


下一篇:小鹿又熬肝写了一份 Vue 2.0 核心原理!