• 深入Vue原理_全面剖析发布订阅模式


    欢迎各位小伙伴们!
    为大家推荐一款刷题神奇哦 点击链接访问牛客网
    各大互联网大厂面试真题。从基础到入阶乃至原理刨析类面试题 应有尽有,赶快来装备自己吧!助你面试稳操胜券,solo全场面试官
    上节中我们提到下方更新的问题:无法做到精准更新

    <div id="app">
      <p v-text="name"></p>
      <p v-text="age"></p>
      <p v-text="name"></p>
    </div>
    <script>
      let data = {
        name: '小兰同学',
        age: 18,
        height: 180
      }
      // 遍历每一个属性
      Object.keys(data).forEach((key) => {
        // key 属性名
        // data[key] 属性值
        // data 原对象
        defineReactive(data, key, data[key])
      })
      function defineReactive(data, key, value) {
        Object.defineProperty(data, key, {
          get() {
            return value
          },
          set(newVal) {
            // 数据发生变化,操作dom进行更新
            if (newVal === value) {
              return
            }
            value = newVal
            compile()
          }
        })
      }
      // 编译函数
      function compile() {
        let app = document.getElementById('app')
        // 1.拿到app下所有的子元素
        const nodes = app.childNodes   //  [text, input, text]
        //2.遍历所有的子元素
        nodes.forEach(node => {
          // nodeType为1为元素节点
          if (node.nodeType === 1) {
            const attrs = node.attributes
            Array.from(attrs).forEach(attr => {
              const dirName = attr.nodeName
              const dataProp = attr.nodeValue
              console.log( dirName,dataProp)
              if (dirName === 'v-text') {
                console.log(`更新了${dirName}指令,需要更新的属性为${dataProp}`)
                node.innerText = data[dataProp]
              }
            })
          }
        })
      }
      // 首次渲染
      compile()
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    发布订阅模式优化

    优化思路思考

    1.数据更新之后实际上需要执行的代码是什么?

    node.innerText = data[dataProp]
    
    • 1

    为了保存当前的node和dataProp,我们再次设计一个函数执行利用闭包函数将每一次编译函数执行时候的node和dataProp都缓存下来,所以每一次数据变化之后执行的是这样的一个更新函数

    () => {
      node.innerText = data[dataProp]
    }
    
    • 1
    • 2
    • 3

    2.一个响应式数据可能会有多个视图部分都需要依赖,也就是响应式数据变化之后,需要执行的更新函数可能不止一个,如下面的代码所示,name属性有俩个div元素都使用了它,所以当name变化之后,俩个div节点都需要得到更新,那属性和更新函数之间应该是一个一对多的关系

    <div id="app">
       <div v-text="name"></div>
       <div v-text="name"></div>
       <p v-text="age"></p>
       <p v-text="age"></p>
    </div>
    
    <script>
      let data = {
         name: 'cp',
         age: 18
      }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    经过分析我们可以得到下面的存储架构图,每一个响应式属性都绑定了相对应的更新函数,是一个一对多的关系,数据发生变化之后,只会再次执行和自己绑定的更新函数

    在这里插入图片描述

    理解发布订阅模式(自定义事件)

    理解发布订阅,关键是理解一对多

    1. 从浏览器事件说起

    dom绑定事件的方式,我们学过俩种

    1. dom.onclick = function(){}
    2. dom.addEventListener(‘click’,()=>{})

    这俩种绑定方式的区别是,第二种方案可以实现同一个事件绑定多个回调函数,很明显这是一个一对多的场景,既然浏览器也叫作事件,我们试着分析下浏览器事件绑定实现的思路

    1. 首先addEventListenr是一个函数方法,接受俩个参数,分别是事件类型回调函数

    2. 因为是一个事件绑定多个回调函数,那在内存里大概会有这样的一个数据结构

      {
        click: ['cb1','cb2',....],
        input: ['cb1','cb2',...]
      }
      
      • 1
      • 2
      • 3
      • 4
    3. 触发事件执行,浏览器因为有鼠标键盘输入可以触发事件,大概的思路是通过事件名称找到与之关联的回调函数列表,然后遍历执行一遍即可

    ok,我们分析了浏览器事件的底层实现思路,那我们完全可以自己模仿一个出来,事件的触发,我们也通过设计一个方法来执行

    2. 实现简单的发布订阅

    // 增加dep对象 用来收集依赖和触发依赖
    const dep = {
        map: Object.create(null),
        // 收集
        collect(dataProp, updateFn) {
          if (!this.map[dataProp]) {
            this.map[dataProp] = []
          }
          this.map[dataProp].push(updateFn)
        },
        // 触发
        trigger(dataProp) {
          this.map[dataProp] && this.map[dataProp].forEach(updateFn => {
            updateFn()
          })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    收集更新函数

    在编译函数执行的时候,我们把用于更新dom的更新函数收集起来

     // 编译函数
      function compile() {
        let app = document.getElementById('app')
        // 1.拿到app下所有的子元素
        const nodes = app.childNodes   //  [text, input, text]
        //2.遍历所有的子元素
        nodes.forEach(node => {
          // nodeType为1为元素节点
          if (node.nodeType === 1) {
            const attrs = node.attributes
            // 遍历所有的attrubites找到 v-model
            Array.from(attrs).forEach(attr => {
              const dirName = attr.nodeName
              const dataProp = attr.nodeValue
              console.log(dirName, dataProp)
              if (dirName === 'v-text') {
                console.log(`更新了${dirName}指令,需要更新的属性为${dataProp}`)
                node.innerText = data[dataProp]
                // 收集更新函数
                dep.collect(dataProp, () => {
                  node.innerText = data[dataProp]
                })
              }
            })
          }
        })
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    触发更新函数

    当属性发生变化的时候,我们通过属性找到对应的更新函数列表,然后依次执行即可

    function defineReactive(data, key, value) {
        Object.defineProperty(data, key, {
          get() {
            return value
          },
          set(newValue) {
            // 更新视图
            if (newValue === value) return
            value = newValue
            // 再次编译要放到新值已经变化之后只更新当前的key
            dep.trigger(key)
          }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6.5 总结

    1. 了解了发布订阅模式的基础形态
    2. 了解发布订阅可以解决什么样的具体问题(精准更新)

    总结

    1. 数据响应式的实现无非是对象属性拦截,我们使用Object.defineProperty来实现,在vue3中使用Proxy对象代理方案进行了优化,解决了Object.defineProperty存在的缺陷

    2. observe对象指的是把数据处理成响应式的对象

      watcher指的其实就是数据变化之后的更新函数 (Vue中的watcher有两种,一种是用来更新视图的watcher,一种是通过watch配置项声明的watcher)

      dep指的就是使用发布订阅实现的收集更新函数和触发更新函数的对象

    3. 指令实现的核心无非是通过模板编译找到标识然后把数据绑上去,等到数据变化之后再重新放一次

    4. 发布订阅模式的本质是解决一对多的问题,在vue中实现数据变化之后的精准更新

    写在最后

    原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

    👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

    ⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

    ✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

    本期推荐

    在这里插入图片描述

    【内容简介】
      本书以理论结合编程开发为原则,使用Python作为开发语言,讲解化算法的原理和应用,详细介绍了Python基础、Gurobi 优化器、线性规划、整数规划、多目标优化、动态规划、图与网络分析、智能优化算法。对于算法部分的每一种算法都包含原理和编程实践,使读者对化算法的认识更加深入。
     
      本书分为 3 篇共 9 章。第 1 篇(第 1~3 章)是化算法与编程基础:第 1 章介绍了什么是化算法及其在生产和生活中的应用;第 2章介绍Python编程基础和Python数据分析库及绘图库;第 3章讲解Gurobi 优化器的基础和高级特性。第 2篇(第 4~6章)是数学规划方法:第 4章详细讲解线性规划的知识,包括单纯形法、内点法、列生成法、拉格朗日乘子法、对偶问题;第 5 章讲解整数规划解法的分支定界法和割平面法;第 6 章讲解多目标优化的概念及基于单纯形法的目标规划法。第 3 篇(第 7~9 章)是启发式算法:第 7 章介绍动态规划算法;第 8 章讲解图与网络分析,介绍*小生成树、短路径、网络流、路径规划等问题的建模;第 9 章讲解了粒子群算法和遗传算法求解各种类型优化算法问题的方法。
      本书内容丰富,实例典型,实用性强,适合各个层次从事
    化算法研究和应用的人员,尤其适合有一定算法基础而没有编程基础的人员阅读。

    【作者简介】
      苏振裕,厦门大学金融学硕士,现任SHEIN 智慧供应链资深算法工程师。知乎专栏《从推公式到写代码》作者,运筹优化论坛(optimize.fun)创建人。在大数据、人工智能、运筹优化和供应链方面,具有多年的相关算法研究应用经验。

  • 相关阅读:
    STM32 printf 重定向到CAN
    autohotkey小脚本集合
    员工离职后,账号权限怎么自动化回收?
    Visual Studio App Center 中的 Bug 跟踪服务
    服务器硬件的基础知识
    Eureka注册中心以及Ribbon负载均衡
    基于SSM的图书管理系统
    人工智能AI 全栈体系(八)
    霍尔电流传感器如何进行可靠性测试?主要应用在哪些领域?
    【刷题篇】回溯算法(深度优先搜索(一))
  • 原文地址:https://blog.csdn.net/JHXL_/article/details/123892945