Vue源码------------- 数据响应系统的基本思路

    在 Vue 中,我们可以使用 $watch 观测一个字段,当字段的值发生变化的时候执行指定的观察者,如下:

1        var vm = new Vue({
2          data: {
3            num:1
4          }
5        })
6        vm.$watch('num',function() {
7          console.log('num被修改')
8        })

     这时候,当我们去修改  num 数值的时候,就会打印出来  'num被修改'。这个到底是如何实现,怎么打印出来的呢?

     现在我们先以另一种方式,讲解期中的道理。关键一个知识点: Object.definePropert; 不了解的先打开这先看下

  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

     假设我们 有下边的数据

1    var  data = {
2       num: 1
3    }

     我们还有一个叫做 $watch 的函数,同时函数接受两个参数;第一个参数是要观测的字段,第二个参数是当该字段的值发生变化后要执行的函数,如下:

1        function $watch () {...}
2        $watch('num', () => {
3          console.log('修改了 num')
4        })

   下边通过 Object.defineProperty 实现下边的功能:

1        Object.defineProperty(data, 'num', {
2          set () {
3            console.log('设置了num')
4          },
5          get () {
6            console.log('读取了 num')
7          }
8        })

    通过 Object.defineProperty  我们可以轻松知道  num 被设置,和读取了。但问题是如何让$watch 方法知道,同时通知第二个参数函数呢?

    有了上边的想法,我们就可以大胆地思考一些事情,比如: 能不能在获取属性 num 的时候收集依赖,然后在设置属性 num的时候触发之前收集的依赖呢?

 1        // dep 数组就是我们所谓的“筐”
 2        const dep = []
 3        Object.defineProperty(data, 'num', {
 4          set () {
 5            // 当属性被设置的时候,将“筐”里的依赖都执行一次
 6            dep.forEach(fn => fn())
 7          },
 8          get () {
 9            // 当属性被获取的时候,把依赖放到“筐”里
10            dep.push(fn)
11          }
12        })

   上边的 fn 来自哪里? 又是在什么时候出发num 属性的get() 呢?

    接下来需要在$watch()上下手:

1     // fn 是全局变量
2     let fn= null
3     function $watch (exp, callback) {
4       // 将 fn 的值设置为 callback
5       fn = callback
6       // 读取字段值 exp,触发 get 函数
7       data[exp]
8     }

   通过上边调用$watch 方法,先给全局变量fn 设置为回调函数,然后读取data的属性,num属性的get方法中,收集callback, 这样当num 变化时候可以通知callback方法;

   上边的方法还有几个问题需要思考:

   1.  实现多个属性监听;2. data 某个属性字段是对象时,3. 确定属性值发生变化,才去出发回调;

    要解决上述问题又要怎么去做呢? 下边封装一个方法:

 1        function observe(data) {
 2          for (let key in data) {
 3            const dep = []
 4            let val = data[key]
 5            // 如果 val 是对象,递归调用 observe 函数将其转为访问器属性
 6            const nativeString = Object.prototype.toString.call(val)
 7            if (nativeString === '[object Object]') {
 8              observe(val)
 9            }
10            Object.defineProperty(data, key, {
11              set:function setter (newVal) {
12                if (newVal === val) return
13                val = newVal
14                dep.forEach(fn => fn())
15              },
16              get:function getter () {
17                dep.push(fn)
18                return val
19              }
20            })
21          }
22        }
23        observe(data)

    Vue中$watch方法第一个参数可以是 data 中的某个属性,function, 以及data属性中 对象的属性 ; 那么这个watch是如何实现呢? 下边我们改变下$watch();

 1        function $watch (exp, callback) {
 2          fn= fcallback
 3          let pathArr,
 4            obj = data
 5          if (typeof exp === 'function') {
 6            exp()
 7            return
 8          }
 9          // 检查 exp 中是否包含 .
10          if (/\./.test(exp)) {
11            // 将字符串转为数组,例:'a.b' => ['a', 'b']
12            pathArr = exp.split('.')
13            // 使用循环读取到 data.a.b
14            pathArr.forEach(p => {
15              obj = obj[p]
16          })
17            return
18          }
19          data[exp]
20        }

     先判断第一个参数 时候为function ,如果为function,则直接调用第一个参数;如果为obj.a 等形式;则进行split分割一层层出发,收集fn;

     最后完整版下如下:

     

 1        var fn = null;
 2        var data = {names:"xiaoming", age:19,obj: {a:1,b:2,c:{c:1,d:2}}}
 3        function observe (data) {
 4          for (let key in data) {
 5            const dep = []
 6            let val = data[key]
 7            // 如果 val 是对象,递归调用 observe 函数将其转为访问器属性
 8            const nativeString = Object.prototype.toString.call(val)
 9            if (nativeString === '[object Object]') {
10              observe(val)
11            }
12            Object.defineProperty(data, key, {
13              set: setter(newVal) {
14                if (newVal === val) return
15                val = newVal
16                dep.forEach(fn => fn())
17              },
18              get: getter() {
19                dep.push(fn)
20                return val
21              }
22            })
23          }
24        }
25 
26        observe(data)
27 
28        function $watch (exp, callback) {
29          fn = callback
30          let pathArr,
31            obj = data
32          if (typeof exp === 'function') {
33            exp()
34            return
35          }
36          // 检查 exp 中是否包含 .
37          if (/\./.test(exp)) {
38            // 将字符串转为数组,例:'a.b' => ['a', 'b']
39            pathArr = exp.split('.')
40            // 使用循环读取到 data.a.b
41            pathArr.forEach(p => {
42              obj = obj[p]
43          })
44            return
45          }
46          data[exp]
47        }
48 
49        $watch('names',function() {
50          console.log('name change')
51        })

    运行:在改变 data.names = '小明'; 
    结果:

    当然Vue实现肯定不会如此简单,接下来有空慢慢细讲,(*^▽^*)

猜你喜欢

转载自www.cnblogs.com/hsp-blog/p/9579830.html
今日推荐