watch 是由用户定义的数据监听,当监听的属性发生改变就会触发回调,这项配置在业务中是很常用。在面试时,也是必问知识点,一般会用作和 computed 进行比较。
那么本文就来带大家从源码理解 watch 的工作流程,以及依赖收集和深度监听的实现。在此之前,希望你能对响应式原理流程、依赖收集流程有一些了解,这样理解起来会更加轻松。
往期文章:
手摸手带你理解Vue响应式原理
手摸手带你理解Vue的Computed原理
watch 用法“知己知彼,才能百战百胜”,分析源码之前,先要知道它如何使用。这对于后面理解有一定的辅助作用。
第一种,字符串声明:
var vm = new Vue({ el: '#example', data: { message: 'Hello' }, watch: { message: 'handler' }, methods: { handler (newVal, oldVal) { /* ... */ } } })第二种,函数声明:
var vm = new Vue({ el: '#example', data: { message: 'Hello' }, watch: { message: function (newVal, oldVal) { /* ... */ } } })第三种,对象声明:
var vm = new Vue({ el: '#example', data: { peopel: { name: 'jojo', age: 15 } }, watch: { // 字段可使用点操作符 监听对象的某个属性 'people.name': { handler: function (newVal, oldVal) { /* ... */ } } } }) watch: { people: { handler: function (newVal, oldVal) { /* ... */ }, // 回调会在监听开始之后被立即调用 immediate: true, // 对象深度监听 对象内任意一个属性改变都会触发回调 deep: true } }第四种,数组声明:
var vm = new Vue({ el: '#example', data: { peopel: { name: 'jojo', age: 15 } }, // 传入回调数组,它们会被逐一调用 watch: { 'people.name': [ 'handle', function handle2 (newVal, oldVal) { /* ... */ }, { handler: function handle3 (newVal, oldVal) { /* ... */ }, } ], }, methods: { handler (newVal, oldVal) { /* ... */ } } }) 工作流程入口文件:
// 源码位置:/src/core/instance/index.js import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' function Vue (options) { this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue_init:
// 源码位置:/src/core/instance/init.js export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // mergeOptions 对 mixin 选项和 new Vue 传入的 options 选项进行合并 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props // 初始化数据 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) } } }initState:
// 源码位置:/src/core/instance/state.js export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) // 这里会初始化 watch if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }initWatch:
// 源码位置:/src/core/instance/state.js function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { // 1 for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { // 2 createWatcher(vm, key, handler) } } }数组声明的 watch 有多个回调,需要循环创建监听
其他声明方式直接创建
createWatcher:
// 源码位置:/src/core/instance/state.js function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { // 1 if (isPlainObject(handler)) { options = handler handler = handler.handler } // 2 if (typeof handler === 'string') { handler = vm[handler] } // 3 return vm.$watch(expOrFn, handler, options) }对象声明的 watch,从对象中取出对应回调
字符串声明的 watch,直接取实例上的方法(注:methods 中声明的方法,可以在实例上直接获取)
expOrFn 是 watch 的 key 值,$watch 用于创建一个“用户Watcher”
所以在创建数据监听时,除了 watch 配置外,也可以调用实例的 $watch 方法实现同样的效果。