vue-watch 原理
首先和数据双向绑定有关系 不懂得可以看一下我之前写得文章,稍作了解
咱们可以首先说一下它得流程
1. initWatch
首先在初始化组件时,会执行这个函数来初始化watch
这个函数的作用就是遍历 watch 的所有属性,执行createWatcher
function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key]
if (isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
2. createWatcher
createWatcher 的主要功能就是确定回调函数和options, 然后通过 $watch 实现监听
function createWatcher(
vm,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
参数介绍
- vm: 组件实例
- expOrFn: watch的 key 它可能是 根对象 也可能是深度的一个对象属性
- handler: 用于 watch 监听的回调函数,可能是对象 也可能是函数,也可能是字符串,如果是对象就可能会配置下个参数
options如果是函数或者字符串 那options参数就是undefined - options: 如果有,就是一个对象 是这样
{deep:false,immediate:false}的 值不一定是false 看开发者的配置
例子
data(){
return {
obj:{
name: 'watch'
}
}
}
// --------------------------------------------------------------------
// expOrFn 是根对象或者 深度的一个对象属性
watch:{
obj:function(new,old){}, // 根对象
'obj.name':function(new,old){}, // 深度监听name
}
// handler 是函数
watch:{
obj:function(new,old){}, // handler 是函数
}
// handler 是对象
watch:{
obj:{ // handler 是对象
hanlder:function(new,old){},
{
deep:true,
immediate:true
}
}
}
// handler 是字符串
methods:{
watchCallback(new,old){}
},
watch:{
obj: watchCallback // handler 是字符串
}
$watch
核心就是这个函数,先来看一下它的源码
Vue.prototype.$watch = function (
expOrFn, // 监听的key
cb, // 监听的回调
options
): 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) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn() {
watcher.teardown()
}
}
因为 $watch 在运行时也可以添加监听属性,所以会在开头先判断一下回调函数是否已经处理过,没有处理过,就调用上一步的函数进行处理。
所以主要函数看后面的代码,用过watch 的都指定 options 中的属性是干什么用的 这里不多介绍! 看**const watcher = new Watcher(vm, expOrFn, cb, options)** 代码
大家先看一下 Wathcer 的源码 ,可以了解到 在 new Watcher 的时候,就会执行 watcher 中 get 方法
先说明一些内容
- this.getter = parsePath(expOrFn) 这段代码主要就是返回一个函数,用于获取 vm上的属性,比如 要获取 obj 或者 obj.name
function (){
return vm['obj']
}
function (){
return vm['obj']['name']
}
- 所以在执行
get时 就是 获取一下data中的属性 get时 第一步就把当前的Watcher 实例推到了全局的target上- 然后执行
value = this.getter.call(vm, vm)获取data中的属性,让data中的属性的Dep 实例收集到 全局的target - 所以当 访问的
data 属性更改时,就会拿到$watch创建的Watcher 实例 - 拿到
Watcher 实例而且Watcher 实例还保存了$watch的回调函数,那不就可以实现 data 属性更改后执行 $watch 的回调函数了吗?
class Watcher implements DepTarget {
vm
expression
cb
id
deep
user
lazy
sync
dirty
active
deps
newDeps
depIds
newDepIds
before
onStop
noRecurse
getter
value
constructor(
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
recordEffectScope(this, activeEffectScope || (vm ? vm._scope : undefined)) // 记录的影响范围
if ((this.vm = vm)) {
if (isRenderWatcher) {
vm._watcher = this
}
}
if (options) {
this.deep = !!options.deep // watch
this.user = !!options.user // watch
this.lazy = !!options.lazy // computed 是true
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 = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = __DEV__ ? expOrFn.toString() : ''
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn) // watch 可以监听 'obj.xx.xx' 解析这样的路径
if (!this.getter) {
this.getter = noop
}
}
this.value = this.lazy ? undefined : this.get()
}
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
throw e
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
}
这行代码主要就是 创建了一个 Watcher 实例, 那在数据双向绑定原理中有提到,new Watcher 后 会执行 watcher.get() 这时就会把 Watcher 实例 推向全局的 target 中,全局的 target 就会将 data 属性中的 dep 收集起来 , data 属性中的 dep 也将全局的 target (也就是 $wather 执行时,new Watcher(vm, expOrFn, cb, options)) 收集起来, 那 data 中可以访问到 Watcher ,Watcher也可以访问 data 形成了相互引用的关系
现在他俩都相互引用到了,就有所关联了,那当data属性修改时,就通过data属性中可以访问到的Watcher让它执行自己的回调就可以了。
最后后面的代码就更简单了, 如果options.immediate == true 那就在初始化的时候先执行一次回调函数,最后返回一个函数用于移除监听
附图 刚刚画的一个图
parsePath 的实现
这个函数很简单,就是通过split 来分割 . 递归的获取到想要的属性
export function parsePath(path) {
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
小小结
不知道大家明白没有,不明白的点可以留言,大家可以结合源码看,效率会高一点
如果不知道如何看源码,我说一下我的方法
- 首先就是把代码从 github 先下载下来,然后先了解一下结构目录,确认一下入口文件
- 源码下载下来会有一个
examples文件夹 就是例子 可以以例子来执行 - 通过断点的方式 找不到上下文就先断点到附近,之后再一点一点的调试
- 实在没啥耐心,就问一下 AI ,多问 AI 先从简单的问起,AI 也懒 刚开始就会说个概念,当你一次有一次的提问,它就会尽可能把它知道的告诉你 然后你再结合之前的方法 再学
- 看源码本来就是很耗时的一件事情 但是提升也是较大的
没看懂的小伙伴不好意思了,耽误大家时间了,如果想看 Vue 其他相关知识 留言~~