通过查阅vue的官方API文档,得知watch选项的定义形式有一下几种
watch: {
// 1. 直接定义处理函数
"watchedData1": function(){}
// 2. 在methods中定义处理函数,在watch中通过函数名访问
"watchedData2": "functionName"
// 3. 以 watch options 的形式定义
"watchedData3": {
handler: function(){},
immediate: true,
deep: true
}
// 4. 处理函数为多个函数,可以通过数组的形式定义
"watchedData4": [
function handler1 () {},
"handler2",
{
handler: function handler3(){},
immediate: true,
deep: true
},
{
handler: "handler4",
immediate: true,
deep: true
}
]
}
那么在vue源码中是怎么处理这些形式的数据的呢?
vue中的初始化选项包括props、data、methods、computed和watch都是在_init()函数中的initState(vm)函数中处理的,_init()函数中所做的事情有:
在core/instance/state.js文件中initWatch方法,是处理watch选项的入口:
function initWatch (vm: Component, watch: Object) {
// 遍历watch属性中的key
for (const key in watch) {
// 监控key属性的处理方法,注意此处的处理方法可以是数组
const handler = watch[key]
//
/**
* 如果是数组,遍历每一个处理函数
* 例如:"watchedData": [
* function handler1 () {},
* "handler2",
* {
* handler: function handler3(){},
* immediate: true,
* deep: true
* },
* {
* handler: "handler4",
* immediate: true,
* deep: true
* }
* ]
*/
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
遍历每一个被监视的数据,为这些数据创建一个或多个Watcher,其中用到的核心函数就是createWatcher:
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
/**
* 如果handler是对象类型
* 例如:
* "watchedData": {
* handler: "functionName",
* immediate: true,
* deep: true
* }
* 将watchedData对应的值赋给options,取出其handler值,作为处理函数
*/
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
/**
* 如果handler是字符串,则从methods中查找对应的方法(因为在初始化过程中,methods的方法被直接挂载到了实例上,所以可以通过vm[handler]获得)
* 例如:"watchedData": "functionName"
*/
if (typeof handler === 'string') {
handler = vm[handler]
}
/**
* 调用vm.$watch时的handler,可能是字符串,可能是函数,也可能是数组
*/
return vm.$watch(expOrFn, handler, options)
}
在createWatcher函数中可以看到,最终还是调用了vm.$watch这个函数创建Watcher的,这个函数在哪里呢?$watch函数是在执行_init()初始化vue实例之前,通过stateMixin挂载到Vue原型上的
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
$watch函数具体是怎么处理的呢
/**
* 由ceateWatcher调用时,cb可能是字符串,可能是函数,也可能是数组
* 当由Vue或vue 实例 vm调用时,cb可能是字符串,可能是函数,可能是数组,也可能是对象
*/
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 处理cb是对象的情况
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// options.user用来表明创建的Watcher是用户创建的(自定义Watcher)还是非用户自定义Watcher(渲染Watcher或computed Watcher)
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果immediate选项为true,立即执行回调函数
if (options.immediate) {
// 因为用户自定义的回调函数存在一定的不确定性,在执行过程中可能会报错,所以用 try catch 进行错误捕获
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
// 返回一个函数,该函数用于卸载watcher对象,解除数据监控
return function unwatchFn () {
watcher.teardown()
}
}
由上可知,initWatch函数在处理watch选项时,是在initWatch函数中处理是否是数组的情况,在createWatcher中处理是否是对象的情况,最后在$watch中创建真正的Watcher