通过查阅vue的官方API文档,得知watch选项的定义形式有一下几种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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选项的入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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原型上的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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函数具体是怎么处理的呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * 由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