深入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, value) {
    Object.defineProperty(data, {
      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.数据更新之后实际上需要执行的代码是什么?

node.innerText = data[dataProp]

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

() => {
  node.innerText = data[dataProp]
}

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. 从浏览器事件说起

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

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

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

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

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

    {
      click: ['cb1','cb2',....],
      input: ['cb1',...]
    }
    
  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()
      })
    }
}

收集更新函数

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

 // 编译函数
  function compile() {
    let app = document.getElementById('app')
    // 1.拿到app下所有的子元素
    const nodes = app.childNodes   //  [text,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]
            })
          }
        })
      }
    })
 }

触发更新函数

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

function defineReactive(data,
      set(newValue) {
        // 更新视图
        if (newValue === value) return
        value = newValue
        // 再次编译要放到新值已经变化之后只更新当前的key
        dep.trigger(key)
      }
    })
}

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}{原创不易,还希望各位大佬支持一下}

相关文章

kindeditor4.x代码高亮功能默认使用的是prettify插件,prett...
这一篇我将介绍如何让kindeditor4.x整合SyntaxHighlighter代...
js如何实现弹出form提交表单?(图文+视频)
js怎么获取复选框选中的值
js如何实现倒计时跳转页面
如何用js控制图片放大缩小