vue源码中watch监听数据的原理
vue中watch的用法
1.字符串形式
1 new Vue({ 2 watch: { 3 name: 'watchFn' 4 }, 5 methods: { 6 watchFn() {} 7 } 8 })
2.函数形式
1 new Vue({ 2 watch: { 3 name: () => {console.log("函数")} 4 } 5 })
3.数组形式 (数组里面的函数会一个接一个执行)
1 new Vue({ 2 watch: { 3 name:[ 4 () => {console.log(111)}, 5 { 6 handler: () => {console.log(222)} 7 } 8 ] 9 } 10 })
4.对象形式
1 new Vue({ 2 watch: { 3 name: { 4 handler: () => {} 5 } 6 } 7 })
源码分析
文件位置 vue/src/core/instance/state
处理不同参数
1 function initWatch(vm, watch) { 2 // 遍历watch的属性 key 3 for( const key in watch) { 4 // handler 类型 字符串|数组|函数|对象 5 const handler = watch[key] 6 if (Array.isArray(handler)) { 7 for (let i = 0; i < handler.length; i++) { 8 createWatcher(vm, key, handler[i]) 9 } 10 } else { 11 createWatcher(vm, key, handler) 12 } 13 } 14 }
处理回调函数,将字符串,对象的回调函数取出来, 调用实例的$watch, 实例化全局watcher
1 // vm, expOrFn => watch的属性 key值, handler => 值 字符串|数组|函数|对象, options 2 function createWatcher (vm, expOrFn, handler, options) { 3 // 判断是不是对象 4 if (isPlainObject(handler)) { 5 options = handler 6 // 回调函数赋值为函数 7 handler = handler.handler 8 } 9 // 字符串直接调去vue实例上的方法 10 if (typeof handler === 'string') { 11 handler = vm[handler] 12 } 13 return vm.$watch(expOrFn, handler, options) 14 }
实例化watcher
1 Vue.prototype.$watch = function (expOrFn,cb,options) { 2 // 此处user为区分是渲染Watcher还是用户传入的watch 3 options.user = true 4 const watcher = new Watcher(vm, expOrFn, cb, options) 5 }
Watcher类里 (此处需要了解Dep跟Watcher的关系)
文件位置 vue/src/core/observe/watcher
1 class Watcher { 2 // expOrFn 为字符串,watch的属性key值 3 constructor(vm, expOrFn, cb, options) { 4 // 是否是用户传入的watcher 5 this.user = !!options.user 6 this.cb = call 7 // parsePath会返回一个函数 执行会触发definePrototype的get方法进行Dep跟Watcher的绑定 8 this.getter = parsePath(expOrFn) 9 //接收初始化的数据 10 this.value = this.get() 11 } 12 get() { 13 // 将全局Watcher Dep.target设置为当前Watcher 14 pushTarget(this) 15 // 此处会访问数据 将Dep跟当前Watcher绑定 16 this.value = this.getter.call(this.vm) 17 popTarget() 18 return value 19 } 20 //数据更改,调用definePrototype set的时候当数据发生改变时会触发这个方法 21 update() { 22 this.run() 23 } 24 run() { 25 //改变之后的数据 26 const value = this.get() 27 //改变前的数据 28 const oldValue = this.value 29 this.value = value 30 if (this.user) {//如果是wacth函数 31 //直接将对应函数执行并且将最新的值和老的值传递过去 32 this.cb.call(this.vm, value, oldValue) 33 } 34 }
parsePath方法
1 function parsePath (path) { 2 if (bailRE.test(path)) { 3 return 4 } 5 // watch 可能会监听 a.b.c 6 const segments = path.split('.') 7 // 返回函数 会触发获取数据 get方法 Dep绑定Watcher 8 return function (obj) { 9 for (let i = 0; i < segments.length; i++) { 10 if (!obj) return 11 obj = obj[segments[i]] 12 } 13 return obj 14 } 15 }