• 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实现肯定不会如此简单,接下来有空慢慢细讲,(*^▽^*)

  • 相关阅读:
    谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family
    谈谈一些有趣的CSS题目(十一)-- reset.css 知多少?
    【Web动画】SVG 实现复杂线条动画
    【Web动画】SVG 线条动画入门
    引人瞩目的 CSS 变量(CSS Variable)
    谈谈一些有趣的CSS题目(十)-- 结构性伪类选择器
    ROW_NUMBER() OVER函数的基本用法
    PL SQL笔记(三)
    pushState、replaceState、onpopstate 实现Ajax页面的前进后退刷新
    无聊的人用JS实现了一个简单的打地鼠游戏
  • 原文地址:https://www.cnblogs.com/hsp-blog/p/9579830.html
Copyright © 2020-2023  润新知