ToC
我想做什么?
在重构公司前同事代码的时候,我发现在 vue@2
中有很多的 watcher
,但在实际业务中,有一些条件是互斥的,比如存在 a
属性后,b
属性就不可能存在,那我就不需要去监听 b
属性的变化了,所以我想要去主动取消在 options API
中 watch
选项中定义的 watcher
。
它在哪里做的?
在明确目的以后,就需要意识到,Vue
是怎么取消组件的 watcher
的。在不了解 Vue 源码的情况下,就需要去了解一下 Vue 组件的创建、执行、挂载、更新和销毁流程,也就是生命周期。这里引用 Vue.js 官网上的生命周期流程图: 从图中可以看到,Vue 对 watch
和对子组件 event
监听的解绑操作在 beforeDestroy
之后,destroyed
之前,有了目标以后,我们就可以在源码中找到其关于生命周期的处理函数的位置了:vue/src/core/instance/lifecycle.js
。文件位置是这个,接下来只需要找到 beforeDestroy
钩子调用的地方,在这个文件的 :102
行有这么一个语句:callHook(vm, 'beforeDestroy')
。
它是怎么做的?
继续在源码中查找关于 watch
的字样,直到找到:
这一段就是取消所有 watcher
的监听。
我该怎么做?
为了验证一下刚找到的这一段代码是否正确,那么我们可以拷贝源码到本地,同时找到在源码中找到的那一段代码的位置,添加一点调试语句,debugger
console.log
或其他的都可以,再写一段代码用于测试。
复制 CDN 链接 里的代码到本地,方便调试。我这里用的 vue 版本是 2.6.14,新建一个 html
文件,写入了类似如下的测试代码:
运行、访问这个文件,当页面中出现了一个按钮后则表示这个实例创建成功。那么就可以开始后续的操作。
代码正常运行后,在浏览器控制台就会有一行 watcher
的打印信息,包含了 val
old
值。尝试打印一下 console.log(vm._watcher, vm._watchers)
是什么。大致结果如下:
一个组件实例中,至少会存在1个 watcher
, 它用于更新视图,而在 watch
配置项中定义的观察者就存在于 _watchers
中。那我们要做的就是找到目标watcher
,并结束它的监听。我们可以尝试一下源码中的操作,看那段源码做了什么事情:
将这段代码放到测试代码中(放在 vm.message = 'Hello World!'
前),刷新页面后就能发现,数据的变化不再能触发视图的更新了,在浏览器中访问 vm
组件实例,查找 count
message
属性,会发现它的值是变化了的。所以可以确定,这段代码就是取消监听的操作。_watcher
属性我们已经看过了,没有眼熟的字段,接下来再去查看 _watchers
。它是一个数组,数组的第二项是 _watcher
属性,还是用于更新视图的,展开第一个watcher
:
看到一个比较熟悉的字段了:expression
,它的值是 message
。我们尝试去修改一下 watch
属性中的 message
键名为:this.message
,再刷新查看第一个 watcher
中的 expression
字段会发现也已经同步变成了 this.message
,那么基本可以肯定,这个watcher
就是和 message
属性绑定的观察者。 在 watcher
的原型上能找到 teardown
方法,它的作用就是取消监听。
那么这一次学习的目的已经达到了,但是每次使用都需要去手动找显然效率不太高,我们可以去封装一个 unwatch
函数来尝试解决这个问题:
调用时只需要传入 vue
实例和 watch
属性中的键名即可:
在点击 unwatch
按钮以后再点击 Add
按钮后控制台将不会有任何输出, 完整的示例代码如下:
2024-01-11 更新
我注意到在 Vue2.7.0 以后的版本因为增加了 setup 语法的原因,重写了一部分编译相关的内容,已经不存在 _watchers
属性了,所以以上的代码需要进行一些调整,兼容一下高版本的 vue,所以 unwatch
代码更新如下:
以上。