Vue关于Watch源码的学习笔记(编辑中)
程序员文章站
2022-03-28 12:31:55
...
Vue中Watch的源码相比于Compute的源码实现简单了很多,以下是我的学习笔记:
一、初始化
1、初始Vue时会通过initState方法,在代码中进行opts.watch字段的判断,从而进行initState方法对Watch进行初始化。(和Compute的一样)。
// 部分实现
function Vue(){
... 其他处理
initState(this)
...解析模板,生成DOM 插入页面
}
function initState(vm) {
...处理 data,props,computed 等数据
if (opts.watch) {
initWatch(this, vm.$options.watch);
}
}
2、initWatch分析(为每个watch创建专门的watcher也就是createWatcher )
// 部分实现
function initWatch(vm, watch) {
for (var key in watch) {
var watchOpt = watch[key];
createWatcher(vm, key, handler);
}
}
function createWatcher(
// expOrFn 是 key,handler 可能是对象
vm, expOrFn, handler,opts
) {
// 监听属性的值是一个对象,包含handler,deep,immediate
// 下面的判断就是获取用户定义的watch函数
if (typeof handler ==="object") {
opts= handler
handler = handler.handler
}
// 回调函数是一个字符串,从 vm 获取
if (typeof handler === 'string') {
handler = vm[handler]
}
// expOrFn 是 key,options 是watch 的全部选项
vm.$watch(expOrFn, handler, opts)
}
3、 vm.$watch(),这里才是watch-watcher的细节实现
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
// 为watch的watcher添加user标志
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// immediate为true时直接回调
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
......
}
我们可以看到当 immediate设置为true时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值。
假设有个data为a,当a被初始化时,其实这个a的 Dep 就直接收集到了 a 对应的 watch-watche。
二、依赖收集与更新
观察 Watcher 源码:
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// options
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 // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
// 对于watch-watcher来说这一步就相当于拿hanlder
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
// 当 watch-watcher 到这一步时,直接调用了 this.get, 相当于直接调用了watch的hanlder
this.value = this.lazy
? undefined
: this.get()
}
}
简单来说当watch-watcher进入时就会触发 this.get()
get () {
pushTarget(this)
let value
const vm = this.vm
try {
// 这一步相当于调用watch的hanlder
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// 若定义了deep,这会遍历watch监听的那个对象的所有key值
if (this.deep) {
// 递归每一个对象或者数组,触发它们的getter,
// 使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
因为this.get(),需要获取到监听的值,这样就相当于触发了监听值的getter,从而触发了 监听值的 dep.depend()
又因为这时候的 Dep.target 指向的是 watch-watcher,这样就相当于监听值 收集到 watch-watcher 进了它的 Dep里
推荐阅读
-
Vue源码学习之关于对Array的数据侦听实现
-
关于Vue中,使用watch同时监听两个值的实现方法
-
关于vue中watch检测到不到对象属性的变化的解决方法
-
Vue 2.0学习笔记之使用$refs访问Vue中的DOM
-
Vue.js中关于侦听器(watch)的高级用法示例
-
Vue 2.0学习笔记之Vue中的computed属性
-
vue.js中关于$watch的oldvalue与newValue的深入讲解
-
Vue学习笔记13-watch监听的使用
-
MySQL源码学习:关于慢查询日志中的Rows_examined=0
-
在Java学习过程中需要积累下来的一些编程思路和方法——关于TextArea的用法和内部元素编辑