watch的源码原理属于比较简单清晰的了
当然要理解还需要配合前面几篇:依赖收集、派发更新、数据劫持
1 watch的入口
源码位置:vue-main\src\core\instance\state.ts
initState() 方法中,判断 opts.watch 如果存在,则执行 initWatch 进行监听器初始化
2 创建 user Watcher
首先 Vue 中定义了 3 个 watcher
- render watcher:模板依赖并且需要显示在视图上变量,其内部保存了一个 render watcher
- computed watcher:计算属性内部保存了一个 computed watcher
- user watcher:使用 watch 侦听的属性内部保存了一个 user watcher
user watcher 就是侦听器创建的,下面我们来看看是怎样实现的
2.1 watch的写法
首先,我们要了解 watch 的几种写法
数组写法:
1 | watch: { |
字符串写法:
1 | watch: { |
2.2 initWatch 初始化
这里用来判断 watch侦听器 的类型
- 如果是数组形式,则遍历,依次创建 Wacther
- 否则,直接创建 Wacther
看看源码:
1 | function initWatch(vm: Component, watch: Object) { |
2.3 创建 Watcher
- 第一步,首先 拿到侦听器的回调函数 handler
- 第二步,将 回调函数 handler 作为参数,去创建 Watcher
第一步:
createWatcher() 方法中源码:
1 | function createWatcher( |
注:回调是字符串的情况
1 | methods: { |
第二步:
然后会执行 vm上的$watch, 去创建 Watcher
源码位置:vue-main\src\core\instance\state.ts
1 | // user置为true 表示创建监听器的watcher |
到这里,user Watcher 就创建好了
3 初始化和更新
3.1 初始化
当我们在代码中使用了 watch侦听器
经过初始化后,创建好了 user Watcher
经过上面的流程后,最终会进入 new Watcher
的逻辑,这里面也是依赖收集和更新的触发点
Watcher 会默认调用 get() 方法,这是 watcher 求值的普遍方法
- 首先 pushTarget 将当前的 user Watcher 挂到
Dep.target
上。 - 然后调用
getter
函数,做依赖收集,拿到新值 - 最后 popTarget() 弹出
1 | get() { |
3.2 更新
当修改侦听的属性后
数据劫持会触发 set
因为前面做过依赖收集了,在派发更新时,会通知 user Watcher 执行 update(最终会执行run方法)
run() 方法中:
- 会触发 get() 取新值 value
- 并缓存旧值 oldValue
- 最后触发回调函数 cb
run() 源码:
1 | run() { |
4 深度监听
深度监听是 watch
监听中一项很重要的配置,它能为我们观察对象中任何一个属性的变化。
目光再拉回到 get() 函数,其中有一段代码是这样的:
1 | if (this.deep) { |
traverse方法源码位置:/src/core/observer/traverse.js
它主要做的事情就是:
递归获取每一项属性,触发它们的“数据劫持get
”收集依赖
About this Post
This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.