【源码】watch 源码解析
Opened this issue · 0 comments
【源码】watch 源码解析
前言
watch
和 computed
一直是我们开发和面试时候经常用的一个点,接下来我们通过源码的解析深入的了解watch
的内部机制
源码解析
我们知道Vue加载的时候会调用initState
来初始化state
// vue/src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
...
initState(vm) // 初始化state
...
}
其实我们的watch
的初始化在initState
内部实现
// vue/src/core/instance/state.js
export function initState (vm: Component) {
... 初始化props、methods、data、computed等
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
我们接下来看下initWatch
是什么名堂
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) { // 如果是数组,遍历调用createWatcher
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
我们会发现一个很有趣的事情,这里做了一个是否是数组的判断,这是因为mixin机制可以让watch是一个数组的形式
上述代码就是遍历数组或者对象,然后调用createWatcher
方法
我们接下来看下createWatcher
方法
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)
}
那么问题来了,为什么createWatcher
方法中还要进行一层类型判断呢?
那是因为我们有3种watch的用法
watch: {
name: {
handler() {}
},
name() {},
name: 'getName',
}
如果是对象的话获取对象的handler
方法,如果是字符串则去实例上取这个方法
最后我们来看最关键的一个方法vm.$watch
(代码做了删减)
Vue.prototype.$watch = function(
// expOrFn 是 监听的 key,cb 是监听回调,opts 是所有选项
expOrFn, cb, opts
){
// expOrFn 是 监听的 key,cb 是监听的回调,opts 是 监听的所有选项
var watcher = new Watcher(this, expOrFn, cb, opts);
// 设定了立即执行,所以马上执行回调
if (opts.immediate) {
cb.call(this, watcher.value);
}
};
首先我们可以发现一点,当我们使用watch
的时候,如果设置immediate
为true,会立刻将handler执行
然后我们会发现watch
的核心, new Watcher()
, 没错,watch
就是通过Vue的发布订阅机制来实现的一个功能点,在源码中给
watch
中的属性新建一个观察者watcher
,然后就可以实现属性变化的时候,执行cd也就是watch
中的handler了,如果对这一块不了解的同学可以看下我的关于Vue中MVVM原理的文章手写mvvm 之 实现数据双向绑定
最后还有一个小知识点,watch
是如何实现深度监听的,也就是我们使用的时候设置的deep
(代码有删减)
Watcher.prototype.get = function() {
Dep.target= this
var value = this.getter(this.vm)
if (this.deep) traverse(value)
Dep.target= null
return value
};
我们可以发现,当设置deep
为true的时候会走traverse
方法
function _traverse (val, seen) {
var i, keys;
var isA = Array.isArray(val);
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); }
}
}
我们可以发现就是一直递归往下查找,这里有一个比较有趣的事情是它通过val[i]
和 val[keys[i]]
变相的获取值,因为data里面的数据是具有响应式能力的,每一次获取都会触发getter
,然后往对应的dep
中添加watcher
, 然后就实现深度监听的能力了
总结
-
watch
内部通过为设置的属性生成一个watcher
的方式实现数据劫持 -
深度监听的原理是通过不断的递归,变相的调用data的getter,然后往对应的dep里面添加watcher