Vue 源码学习之依赖收集和侦听属性

上个学习笔记讲到 Vue 的响应式数据原理,我们知道 vue 通过 Object.defineProperty对数据进行 getset来完成数据的响应式,而从源码中我们得知,每个对象进行响应式处理时会有各自的 dep实例,在 get的时候,会调用 dep.depend来收集依赖,在 set的时候执行 dep.notify来更新依赖,这期学习笔记将介绍收集依赖的主要过程;

一、依赖收集

我们可以先看看 Dep 的主要内容

// src/core/observer/dep.js
export default class Dep {
    
    
    static target: ?Watcher;
    id: number;
    subs: Array<Watcher>;
    constructor () {
    
    
        this.id = uid++
        // 用来收集依赖,里面存放 watcher 实例
        this.subs = []
    }
	// 添加依赖
    addSub (sub: Watcher) {
    
    
        this.subs.push(sub)
    }
	// 移除依赖
    removeSub (sub: Watcher) {
    
    
        remove(this.subs, sub)
    }
    depend () {
    
    
        // 如果当前全局存在依赖的 watcher,则添加到 subs 
        if (Dep.target) {
    
    
            Dep.target.addDep(this)
        }
    }
    notify () {
    
    
        const subs = this.subs.slice()
        // 通知依赖
        for (let i = 0, l = subs.length; i < l; i++) {
    
    
            subs[i].update()
        }
    }
}
// Dep.target 是全局唯一的 watcher 指向
Dep.target = null
// 模拟栈结构来存放 watcher
const targetStack = []

export function pushTarget (target: ?Watcher) {
    
    
    targetStack.push(target)
    Dep.target = target
}

export function popTarget () {
    
    
    targetStack.pop()
    Dep.target = targetStack[targetStack.length - 1]
}

从上面的源码中我们可以看到 Dep的主要作用是用一个数组收集 watcher实例,当我们调用 dep.depend的时候可以将当前存在的 watcher收集到 subs,然后在 dep.notify的时候遍历 subs,依次通知 watcher实例调用 update更新,那 Watcher 是怎么定义的呢?继续往下走:

// src/core/observer/watcher.js
export default class Watcher {
    
    
    constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
    ) {
    
    
        this.vm = vm
        if (isRenderWatcher) {
    
    
            vm._watcher = this
        }
        vm._watchers.push(this)
        if (options) {
    
    
            this.deep = !!options.deep
            this.user = !!options.user
            this.lazy = !!options.lazy
            this.sync = !!options.sync
            this.before = options.before
        } else {
    
    
            this.deep = this.user = this.lazy = this.sync = false
        }
        this.cb = cb
        this.id = ++uid 
        this.active = true
        this.dirty = this.lazy // 计算属性时使用
        this.deps = [] // 存放 dep
        this.newDeps = [] 
        this.depIds = new Set() // 去重
        this.newDepIds = new Set()
        this.expression = process.env.NODE_ENV !== 'production'
            ? expOrFn.toString()
        : ''
        if (typeof expOrFn === 'function') {
    
    
            this.getter = expOrFn
        } else {
    
    
            // 传进来的如果是对象中的某个值,如 form.name,Vue 会将字符串转化为实例对应的值
            this.getter = parsePath(expOrFn)
            if (!this.getter) {
    
    
                this.getter = noop
                process.env.NODE_ENV !== 'production' && warn(
                    `Failed watching path: "${
      
      expOrFn}" ` +
                    'Watcher only accepts simple dot-delimited paths. ' +
                    'For full control, use a function instead.',
                    vm
                )
            }
        }
        // 如果不是计算属性,则调用一次 get 方法,获取并保存一次旧值
        this.value = this.lazy
            ? undefined
        : this.get()
    }
    get () {
    
    
        // 将当前的 watcher 实例挂到 Dep.target
        pushTarget(this)
        let value
        const vm = this.vm
        try {
    
    
            value = this.getter.call(vm, vm)
        } catch (e) {
    
    
            if (this.user) {
    
    
                handleError(e, vm, `getter for watcher "${
      
      this.expression}"`)
            } else {
    
    
                throw e
            }
        } finally {
    
    
            if (this.deep) {
    
    
                // 如果 deep 属性为真,则递归对象的嵌套属性,进行深度依赖收集
                traverse(value)
            }
            // 将当前的 watcher 从 Dep.target 中移除
            popTarget()
            this.cleanupDeps()
        }
        return value
    }

    addDep (dep: Dep) {
    
    
        const id = dep.id
        if (!this.newDepIds.has(id)) {
    
    
            this.newDepIds.add(id)
            this.newDeps.push(dep)
            if (!this.depIds.has(id)) {
    
    
                // 将当前实例化的 watcher 添加到 dep 的 subs 中
                dep.addSub(this)
            }
        }
    }

    cleanupDeps () {
    
    
        let i = this.deps.length
        while (i--) {
    
    
            const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) {
    
    
                dep.removeSub(this)
            }
        }
        let tmp = this.depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        this.newDepIds.clear()
        tmp = this.deps
        this.deps = this.newDeps
        this.newDeps = tmp
        this.newDeps.length = 0
    }
	// 更新渲染 watcher
    update () {
    
    
        if (this.lazy) {
    
    
            this.dirty = true
        } else if (this.sync) {
    
    
            this.run()
        } else {
    
    
            queueWatcher(this)
        }
    }

    run () {
    
    
        if (this.active) {
    
    
            // 获取新的值
            const value = this.get()
            if (
                value !== this.value ||
                isObject(value) ||
                this.deep
            ) {
    
    
                // 旧的值
                const oldValue = this.value
                this.value = value
                // 如果是用户 watcher,执行用户传进来的回调函数,并将新值旧值传出去
                if (this.user) {
    
    
                    const info = `callback for watcher "${
      
      this.expression}"`
                    invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
                } else {
    
    
                    // 执行渲染 watcher
                    this.cb.call(this.vm, value, oldValue)
                }
            }
        }
    }
    // 计算属性时使用
    evaluate () {
    
    
        this.value = this.get()
        this.dirty = false
    }
	// 计算属性时使用
    depend () {
    
    
        let i = this.deps.length
        while (i--) {
    
    
            this.deps[i].depend()
        }
    }
	// 销毁 watcher,在 destory 生命钩子函数调用
    teardown () {
    
    
        if (this.active) {
    
    
            if (!this.vm._isBeingDestroyed) {
    
    
                remove(this.vm._watchers, this)
            }
            let i = this.deps.length
            while (i--) {
    
    
                this.deps[i].removeSub(this)
            }
            this.active = false
        }
    }
}

Watcher类来看,我们主要是通过 get方法来获取新的值,在数据更新时调用 update来渲染 watcher,除了这些以外,还有其他的属性和方法,这些在计算属性的时候会用到,后面再补充;

二、侦听属性

在之前的文章里面,我们知道 Vue 在初始化的时候会调用 initWatchwatch属性进行初始化,初始化的时候做了些什么,我们可以从源码中查看:

// src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
    
    
    for (const key in watch) {
    
    
        const handler = watch[key]
        // 自定义 watch 的写法可以是数组、对象、函数、字符串
        if (Array.isArray(handler)) {
    
    
            for (let i = 0; i < handler.length; i++) {
    
    
                // 遍历数组创建 watcher
                createWatcher(vm, key, handler[i])
            }
        } else {
    
    
            createWatcher(vm, key, handler)
        }
    }
}
// 创建 watcher 的核心方法
function createWatcher (
	vm: Component,
     expOrFn: string | Function,
     handler: any,
     options?: Object
) {
    
    
    // 如果传入的是对象,例如:
    /** 
    	watchData: {
            handler(nv, ov) {
                ...
            },
            deep: true,
            immediate: true
        }
	**/
    if (isPlainObject(handler)) {
    
    
        options = handler
        handler = handler.handler
    }
    // 如果传入的是字符串,则 handler 为定义好的方法
    if (typeof handler === 'string') {
    
    
        handler = vm[handler]
    }
    return vm.$watch(expOrFn, handler, options)
}

// $watch 原型方法是创建用户自定义 watch 的核心
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    
    
    const vm: Component = this
    // 如果 handler 是对象,需要调用一次 createWatcher,拿到处理好的参数
    if (isPlainObject(cb)) {
    
    
        return createWatcher(vm, expOrFn, cb, options)
    }
    // 用户自定义选项,包括 deep, immediate等
    options = options || {
    
    }
    // 用户自定义 watch 标识
    options.user = true
    // 传入相应的参数,创建 watch 实例
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 如果 immediate 为 true,则马上执行一次渲染 watcher
    if (options.immediate) {
    
    
        const info = `callback for immediate watcher "${
      
      watcher.expression}"`
        pushTarget()
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
        popTarget()
    }
    return function unwatchFn () {
    
    
        watcher.teardown()
    }
}

从源码中我们可以得知几个事情:

  • initWatch会遍历用户自定义的 watch,挨个进行创建,我们知道自定义 watch的写法可以是数组、对象、函数、字符串,创建实例时会进行兼容性处理,如果是数组,则遍历数组进行创建;
  • createWatcher方法是创建 watch的核心,主要是对 handler进行判断处理,然后通过 vm.$watch创建实例;
  • $watch原型方法是创建用户自定义 watch 的核心,它会给 options打上标识 user: true,表示是用户自定义 watch,然后通过 new Watcher创建一个实例,如果设置了 immediate,则执行一次用户传进来的回调函数;

总结:

  • Vue 在数据响应式处理时,给每个数据创建一个 dep 的实例,用来收集依赖,当数据变化时,通过调用 dep.notify来通知 watcher进行更新,watcher更新时主要方法是 getupdate,通过 get来获取新的值,通过 update来执行渲染 watcher
  • Vue 在初始化时会执行 initWatch,对用户自定义的 watch进行兼容性处理,通过 createWacther方法来创建 watchcreateWacther方法会将处理好的参数通过 vm.$watch原型来创建 watch实例;

猜你喜欢

转载自blog.csdn.net/Ljwen_/article/details/124712073