• VUE框架响应系统简单实现


    前言

    所谓的前端响应系统,简单的理解可以认为,浏览器根据特定变量显示的数据,在这个变量的值发生变化后,能够自动的刷新页面从而展现出变化后的新值。这也是响应前端框架实现双向绑定的前提。

    示例

    如下示例:通过setTimeout定时器在2秒后更改变量的值,从而实现界面在2秒后能够自动从“Hello world”变化为“你好,世界”
    在这里插入图片描述

    原理分析

    提起响应系统,耳熟能详的就是数据劫持(数据代理),所谓的数据劫持就是指,在访问或者修改对象某个属性时,通过一段代码拦截这个行为,并进行相应的操作返回结果。而前端响应系统,主要就是根据数据劫持这个原理实现的,具体的逻辑可以拆分为以下几步

    1. 初始化读取对象属性值
    let obj1 = { text: "Hello world" }
    function effect() {
      document.getElementById("toshow").innerHTML = obj1.text
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 劫持读取行为添加相应操作(将该读取行为暂存起来)
    const buckset = new Set()
    buckset.add(effect)
    
    • 1
    • 2
    1. 设置对象属性的值
    obj1.text = '你好 世界'
    
    • 1
    1. 劫持设置行为添加相应操作(设置完成后并调用之前暂存的读取操作)
    buckset.forEach(fn => fn())
    
    • 1

    上述步骤就是响应系统最简易的基础逻辑

    代码示例

    在 ES2015 以前主要是通过Object.defineProperty()方法修改对象属性的get()方法和set()方法实现劫持读取对象属性值和设置属性值的行为,进而添加额外的逻辑实现主动响应,这也是VUE2的实现方式。在 ES2015版本及以后新增了Proxy代理对象,可以在代理的对象种新增属性的读取和设置行为,从而实现主动响应,VUE3采取的正式这种方式。Object.defineProperty()和Proxy的实现方式分别如下,也可以认为下面两个是两个简易的响应系统:

    数据劫持实现方式
    在线代码

    <!DOCTYPE html>
    <html>
      <body>
        <h3>响应系统基础原理</h3>
        <br />
        <span>
          2秒后自动更换:
          <h4 id="toshow"></h4>
        </span>
        <script>
          // 初始化数据
          let obj1 = { text: 'Hello world' }
    
    	  // 读取操作
          function effect() {
            document.getElementById('toshow').innerHTML = data.text
          }
    
          // 创建“桶” 用于暂存读取操作
          const buckset = new Set()
    
          // 劫持读取和操作行为
          function newObj(obj = {}) {
            // 遍历对象键, 根据键劫持对象get和set方法
            const keys = Object.keys(obj)
           
            keys.forEach(k => {
              // 获取初始值
              const val = obj[k]
              Object.defineProperty(obj, k, {
                get: function() {
                  // 暂存读取行为
                  buckset.add(effect)
                  // 读取行为正常返回
                  return this.k ? this.k : val
                },
    
                set: function(value) {
                  // 设置行为正常设置
                  this.k = value
                  // 执行读取行为
                  buckset.forEach(fn => fn())
                }
              })
            })
            return obj
          }
    
    	  // 执行劫持行为
          let data = new newObj(obj1)
    
          // 初始化读取对象
          effect()
    
          // 修改对象值
          setTimeout(() => {
            data.text = '你好,世界'
          }, 2000)
        </script>
      </body>
    </html>
    
    • 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
    • 59
    • 60
    • 61

    数据代理实现方式
    在线代码

    <!DOCTYPE html>
    <html>
      <body>
        <h3>响应系统基础原理</h3>
        <br />
        <span>
          2秒后自动更换:
          <h4 id="toshow"></h4>
        </span>
        <script>
          // 初始化数据
          let obj = { text: 'Hello world' }
    
          // 读取操作
          function effect() {
            document.getElementById('toshow').innerHTML = data.text
          }
    
          // 创建“桶” 用于暂存读取操作
          const buckset = new Set()
    
          // 代理读取和操作行为
          const data = new Proxy(obj, {
            get(target, key) {
              // 暂存读取行为
              buckset.add(effect)
              // 读取行为正常返回
              return target[key]
            },
    
            set(target, key, newVal) {
              // 设置行为正常设置
              target[key] = newVal
              // 执行读取行为
              buckset.forEach(fn => fn())
              return true
            }
          })
    
          // 初始化读取对象
          effect()
    
          // 修改对象值
          setTimeout(() => {
            data.text = '你好,世界'
          }, 2000)
        </script>
      </body>
    </html>
    
    • 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

    最后

    当然,这仅仅只是最基础的响应系统原理,对于前端框架来说,其功能还是远远不够的,比如如何避免陷入无限递归、如何避免重复调用、如何实现嵌套响应等等,要优化的空间还是非常大。但是通过本文,我们至少有了一个清晰的印象,知道了所谓响应系统的大概实现逻辑,不至于以后在面对响应、数据劫持和数据代理时一头雾水。

    如果想对VUE的响应系统有跟深的理解和学习推荐阅读霍春阳编写的《Vue.js设计与实现》第四章响应系统的作用与实现。
    如果本文对你有帮助,请动动鼠标点赞加收藏,谢谢😊!

  • 相关阅读:
    设计模式 -- 单例模式(Singleton Pattern)
    [运维|数据库] mysql中的READS SQL DATA修饰符说明
    java毕业设计奢品网站系统源码+lw文档+mybatis+系统+mysql数据库+调试
    解决安装nvm以后windows cmd无法找到npm/yarn命令的问题
    《凤凰架构》读书笔记 —— 本地事务如何实现原子性和持久性?
    遥控车模的电机控制器
    一篇学会JUC、JVM 吊打面试官!
    阿里云新用户:定义,专享福利及优惠活动
    Bag of Tricks for Efficient Text Classification(FastText)
    hdfsClient_java对hdfs进行上传、下载、删除、移动、打印文件信息尚硅谷大海哥
  • 原文地址:https://blog.csdn.net/My_Soul_/article/details/127584455