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 }

相关