Vue源码学习8-响应式原理-watch
前言
前面的文章已经讲过了 render-watcher 和 computed-watcher,如果能轻易的理解前两种 watcher ,本篇介绍的 user-watcher 真的是非常的简单,代码不多,逻辑简单,跟着源码走一边就清楚了
--以下所有代码来自Vue 2.6.11 版本
--以下的vm都代指vue实例
initWatch
在Vue中我们有几种不同使用 watch 的方式,具体怎么写的看文档
不管用那种方式,它们底层的逻辑都是一样的
我们先来看看第一种方式,配置项声明的方式 ,它的初始化逻辑是在 initWatch 中发生的,它的初始化发生在 initComputed 之后,所有它能监听 data/props 也能监听 computed 示例代码:
{ data:{ name1:'Tifa', name2:'Cloud', person:{ name:'Aerith' } }, watch:{ name1:function(newVal){ console.log(newVal); }, name2:[.....], person:{ deep:true, callback } } }
1:进入initWatch 方法中(src/core/instance/state.js),就是一个简单的遍历,然后循环执行 createWatcher 方法(handler 可以是一个 function 也可以是一个 Array,还可以是一个对象)
function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } }
2:进入 createWatcher(src/core/instance/state.js),走到最后我们发现,内部其实调用了 vm.$watch,也就是Vue的检测器提供的第二种写法了,那接下来只需要梳理 $watch 的逻辑就OK了
function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
3:找到 $watch 定义的地方(还是在这里面 src/core/instance/state.js),代码不多,逻辑也很简单
options.user = true //这里标识 Watcher 为user-watcher
接着就是熟悉的 new Watcher 的过程了,这次的入参也是有些许的不同
4:进入构造函数中,可以看到这一次的 this.cb = cb ,cb不再是空函数了,就是我们自己写的监听函数
走到 if(typeof expOrFn === 'function') 判断逻辑,expOrFn 不在是function而是一个字符串,parsePath 是用来解析这个字符串(代码贴在了下方),具体怎么解析的就自己细看了
5:走到构造函数的最后,还是熟悉的 get 方法,压栈-依赖收集-出栈 一系列操作,user-watcher 的 依赖收集在此完成,最后,watcher.value 会赋值为被监听的字段的值,以上面的示例代码为例,name1 的侦听器对应的 user-watcher,它的 watcher.value = 'Tifa'
6:走完 new Watcher ,回到 $watch ,可以看到一个很熟悉的字段:immediate,在Vue文档中它代表着初始化完成后会立即执行侦听器的回调函数
7:最后返回一个方法,这个方法是用来销毁当前 user-watcher 的,至于teardown 方法的具体逻辑就自行查看就行了,它执行完之后,这个user-watcher的所有依赖都会清除掉,包括 user-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) } options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } return function unwatchFn () { watcher.teardown() } }
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`) export function parsePath (path: string): any { if (bailRE.test(path)) { return } const segments = path.split('.') return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj } }
派发更新
这里的派发更新指的是 user-watcher 触发侦听函数的过程,它和 render-watcher 类似,当收到 Dep 的 notify 后都会执行 watcher.update 方法,然后被加入到下一个事件循环中,也就是说 侦听器也是异步触发的,它们最后都会走到 watcher.run 方法
进入 Watcher的run查看
1:get 取值,顺便开启新一轮 依赖收集,完事儿之后进入 下面的 if 逻辑
2:前面在讲 render-watcher 的时候,我们这里的 if 判断是进不去的,因为这里是给 user-watcher 使用的
3:接着就是触发回调函数的逻辑,也就是用户自己定义的 侦听回调函数,入参为 newVal 和 oldVal
至此,一个简单的 user-watcher 的逻辑就走完了,真的挺简单的。
deep
watch 的 deep 这个字段平时开发可能用的比较少,但难免会遇到有需求的时候,他是干嘛的呢?复制一段 Vue 的文档,再贴一段示例代码
为了发现对象内部值的变化,可以在选项参数中指定 deep: true
。注意监听数组的变更不需要这么做。
我们监听的 person 对象,但是当它内部的name发生变化的时候,callback也被触发了,这就是deep的功能
vm.$watch('person', callback, {
deep: true
});
vm.person.name = 'Aerith';
接下来看看源码是怎么做的
打开 Watcher 的源码看到 get 方法,可以很明显的看到一段 this.deep 的判断逻辑,然后执行 traverse 方法
1:进入这个方法中(源码如下,src/core/observer/traverse.js)
2:seenObjects 是一个类型为 Set 的空集合,_traverse 是一个用来递归操作的方法
3:我们以上面那段 深度监听的代码为例,此时的val就是那个 person 对象({name:'Aerith'}),第一个终止程序的逻辑可以跳过了
4:此时 val 是一个带有 __ob__ 标记的响应式对象,所有第二个 if 逻辑是可以进去的,就是一个简单的排除重复的操作,
5:接着就是第三个 if 逻辑(这里是重点),这里 val 不管是Array还是Object类型,都是差不多的一个遍历递归逻辑,然后注意了,这里会有个获取值的操作 val[i] 或者 val[keys[i]],取值就意味着会触发 Object.defineProperty 的 get 逻辑进行依赖收集,正好我们的 user-watcher 在 get 方法的时候已经压栈了,所以当我们递归获取 person 对象的所有值的时候,person 对象下的每一个 dep 都会被 user-watcher 收集建立依赖。至此,深度侦听的原理就已经搞清楚了,当 person 对象下的每一个字段被改变时,都会对这个 user-watcher 发出通知。
export function traverse (val: any) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse (val: any, seen: SimpleSet) { let i, keys const isA = Array.isArray(val) if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } if (val.__ob__) { const depId = val.__ob__.dep.id if (seen.has(depId)) { return } seen.add(depId) } if (isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
总结
data、props、computed、watch,render-watcher,computed-watcher,user-watcher 这些东西已经说讲完了,Vue的响应式原理的内容到这里基本也就完了
发表回复