Dependency collection and listening attributes of Vue source code learning

The last study note talked about the principle of Vue’s responsive data . We know that Vue completes the data’s responsiveness by Object.definePropertyperforming andget on the data. From the source code, we know that each object will have its own instance when performing responsive processing. When , will call to collect dependencies, and execute to update dependencies when , this study note will introduce the main process of collecting dependencies;setdepgetdep.dependsetdep.notify

1. Rely on collection

We can first look at the main content of 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]
}

From the above source code, we can see Depthat the main function of is to use an array to collect watcherinstances. When we dep.dependcall we can watchercollect the currently existing subs, and dep.notifythen traverse when subs, and notify watcherthe instance to call updateupdate in turn. How is Watcher defined? Woolen cloth? Go on down:

// 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
        }
    }
}

From the perspective of Watcherthe class , we mainly use getthe method to obtain new values, and call updateto watcher. In addition to these, there are other attributes and methods, which will be used when calculating attributes, and will be added later;

Two, listening properties

In the previous article, we know that Vue will call initWatchto watchinitialize the property when it is initialized. What is done during initialization, we can check it from the source code:

// 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()
    }
}

From the source code we can learn several things:

  • initWatchIt will traverse user-defined ones watchand create them one by one. We know that watchcustom can be written in arrays, objects, functions, and strings. Compatibility processing will be performed when creating an instance. If it is an array, it will be created by traversing the array;
  • createWatcherThe method is the core watchof , mainly to handlerjudge and process , and then vm.$watchcreate an instance;
  • $watchThe prototype method is the core watch of , it will optionsmark the user: true, indicating that it is user-defined watch, and then new Watchercreate an instance, if set immediate, execute the callback function passed in by the user once;

Summarize:

  • When Vue responds to data processing, it creates an instance depof to collect dependencies. When the data changes, it is dep.notifynotified by calling watcherto update. watcherThe main method of updating is getand update, getthrough to get new values, through updateto perform rendering watcher;
  • Vue will execute it when it is initialized initWatch, watchperform , and createWacthercreate it through the method watch, createWactherwhich will use the processed vm.$watchparameters to create watchan instance through the prototype;

Guess you like

Origin blog.csdn.net/Ljwen_/article/details/124712073