vue响应式原理的实现

vue响应式原理的实现Vue最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。—-官方文档引言Vue的数据双向绑定,响应式原理,其实就是通过Object.defineProperty()结合发布者订阅者模式来实现的。Observer通过O…

大家好,又见面了,我是你们的朋友全栈君。

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。—-官方文档

引言

Vue的数据双向绑定,响应式原理,其实就是通过Object.defineProperty()结合发布者订阅者模式来实现的。

  • Observer 通过Object.definePropty进行数据劫持
  • Dep 发布者,添加订阅者以及在数据发生改变的时候通知所有的订阅者
  • Watcher 订阅者,对数据进行观察以及保存数据修改需要触发的回调
  • Compiler 模板编译器,对HTML模板进行编译,提取其中的变量并转化为数据(绑定更新Watcher 订阅者)。

在这里插入图片描述
本文整理的较为粗糙,大体的说明了一下响应式的实现过程,很多地方直接使用模拟数据,不过整体的流程还是比较清晰。


数据劫持

听起来这个词挺唬人的,换句话讲就是如何对监听一个对象的改变?
其实有两种办法:使用Object.defineProperty和ES6的Proxy ,这里就用第一种方式来实现的

	class Vue { 
   
      constructor(options) { 
   
        this.data = options.data;
        observe(this.data);

        // 模拟 { 
   {name}} { 
   {age}}
        new Watcher(this.data,'name');
        new Watcher(this.data,'name');

        new Watcher(this.data,'age');
        new Watcher(this.data,'age');
      }
    }
    function observe(obj) { 
   
      // 判断类型
      if (!obj || typeof obj != 'object') { 
   
        return
      }
      Object.keys(obj).forEach(item => { 
   
        defineReactive(obj, item, obj[item])
      })
      // 监听对象属性的的变化
      function defineReactive(obj, key, value) { 
   
        // 递归子属性
        observe(value);
        let dp = new Dep();
        // 监听对象属性
        Object.defineProperty(obj, key, { 
   
          enumerable: true, //属性可枚举(可遍历)
          configurable: true, //属性可配置(比如可以删除)
          // 获取属性时 会调用get
          get() { 
   
            if (Dep.target) { 
   
              dp.addSub(Dep.target) // 新增
            }
            return value
          },
          // 设置属性时 会调用set
          set(newVal) { 
   
            observe(newVal) //如果赋值是一个对象,也要递归子属性
            if (newVal != value) { 
   
              value = newVal;
              dp.notify() //新增
            }
          }
        })
      }
    }
  • Object.keys(obj).forEach 对对象里的每个属性进行遍历监听。
  • Object.defineProperty(obj, key,{}) 对属性进行getter/setter的监听
    注意! 无法检测到对象属性的添加或删除 这是因为 Vue 通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性(可以使用Vue.set(location, a, 1))。
  • Dep.target是什么?
    每次对一个属性进行监听,都需要创建对应的Dep发布者,来存储对应属性的所有watcher订阅者,watcher订阅者调用的时候设置Dep.target指向自己,get()每次调用的时候只需要push Dep.target即可 因为指向的就是watcher本身,Dep.target主要的作用就是代指某个watcher,当添加完之后设置Dep.target = null。

Dep发布者

收集所有订阅者以及触发订阅者的更新,其实它是订阅者和数据之间的一个调度中心,用于集中处理一些事情。

class Dep { 
   
      constructor() { 
   
        /* 用来存放Watcher对象的数组 */
        this.subs = [];
      }
      /* 在subs中添加一个Watcher对象 */
      // 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作
      addSub(sub) { 
   
        this.subs.push(sub);
      }
      /* 通知所有Watcher对象更新视图 */
      // 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作。
      notify() { 
   
        this.subs.forEach((sub) => { 
   
          sub.update();
        })
      }
    }

Watcher订阅者

当属性发生变化后,我们要通知用到数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。

然后,我们在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个,再由它负责通知其他地方。

比如说{
{message}}这个变量 在头部、尾部、导航中都有用到,那{
{message}}改变的时候如何同时所有调用它的地方呢? watcher是每个{
{message}} Dep包含所有watcher的集合。


class Watcher { 
   
      constructor(obj, key) { 
   
        Dep.target = this;// 将 Dep.target 指向自己
        this.obj = obj;
        this.key = key;
        this.value = obj[key];// 然后触发属性的 getter 添加监听 这里访问了this.data的属性
        Dep.target = null;// 最后将 Dep.target 置空
      }
      update() { 
   
        // 获得新值
        this.value = this.obj[this.key]
        // 后期定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
        console.log('watcher订阅者更新的内容 this.value',this.value);
      }
    }

总结

这是细化后的响应式流程
在这里插入图片描述

上面完整的代码

    /** * 订阅者 Dep * 收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。 * 于是我们先来实现一个订阅者 Dep 类,用于解耦属性的依赖收集和派发更新操作,说得具体点,它的主要作用是用来存放 Watcher 观察者对象。我们可以把Watcher理解成一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。 * */
    // Dep的简单实现

    class Dep { 
   
      constructor() { 
   
        /* 用来存放Watcher对象的数组 */
        this.subs = [];
      }
      /* 在subs中添加一个Watcher对象 */
      // 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作
      addSub(sub) { 
   
        this.subs.push(sub);
      }
      /* 通知所有Watcher对象更新视图 */
      // 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作。
      notify() { 
   
        this.subs.forEach((sub) => { 
   
          sub.update();
        })
      }
    }
    // Watcher
    /** * Vue 中定义一个 Watcher 类来表示观察订阅依赖。至于为啥引入Watcher * 当属性发生变化后,我们要通知用到数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。 * 然后,我们在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个,再由它负责通知其他地方。 * */

    class Watcher { 
   
      constructor(obj, key) { 
   
        // 将 Dep.target 指向自己
        // 然后触发属性的 getter 添加监听
        // 最后将 Dep.target 置空
        Dep.target = this;
        this.obj = obj;
        this.key = key;
        this.value = obj[key];
        Dep.target = null;
      }
      update() { 
   
        // 获得新值
        this.value = this.obj[this.key]
        // 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
        console.log('watcher订阅者更新的内容 this.value',this.value);
      }
    }



    class Vue { 
   
      constructor(options) { 
   
        this.data = options.data;
        observe(this.data);

        // 模拟 { 
   {name}} { 
   {age}}
        new Watcher(this.data,'name');
        new Watcher(this.data,'name');

        new Watcher(this.data,'age');
        new Watcher(this.data,'age');
      }
    }
    function observe(obj) { 
   
      // 判断类型
      if (!obj || typeof obj != 'object') { 
   
        return
      }
      Object.keys(obj).forEach(item => { 
   
        defineReactive(obj, item, obj[item])
      })
      // 监听对象属性的的变化
      function defineReactive(obj, key, value) { 
   
        // 递归子属性
        observe(value);
        let dp = new Dep();
        // 监听对象属性
        Object.defineProperty(obj, key, { 
   
          enumerable: true, //属性可枚举(可遍历)
          configurable: true, //属性可配置(比如可以删除)
          // 获取属性时 会调用get
          get() { 
   
            if (Dep.target) { 
   
              dp.addSub(Dep.target) // 新增
            }
            return value
          },
          // 设置属性时 会调用set
          set(newVal) { 
   
            observe(newVal) //如果赋值是一个对象,也要递归子属性
            if (newVal != value) { 
   
              value = newVal;
              dp.notify() //新增
            }
          }
        })
      }
    }

    const app = new Vue({ 
   
      data: { 
   
        name: 'qfl',
        age:'26',
        location: { 
   
          x: 100,
          y: 100
        },
        arr: ['1', '2']
      }
    })


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/132934.html原文链接:https://javaforall.net

(0)
上一篇 2022年5月1日 上午11:20
下一篇 2022年5月1日 上午11:20


相关推荐

  • 数论四大定理之威尔逊定理

    数论四大定理之威尔逊定理本文总结了网上关于威尔逊定理的证明 用逻辑更通顺的数学语言表述出来 仅供参考威尔逊定理 ppp 为质数 p 1 1 mod amp amp amp amp amp amp amp ThinSpace amp amp amp amp amp amp amp ThinSpace p Longleftrigh p 1 equiv 1 modp p 1 1 modp 证明 必要性 p 1

    2026年3月18日
    2
  • Java内存管理-Stackoverflow问答-Java是传值还是传引用?(十一)

    勿在流沙筑高台,出来混迟早要还的。做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!本文导图:一、由一个提问引发的思考在Stack Overflow 看到这样一个问题:Is Java “pass-by-reference” or “pass-by-value”?翻译成中文:Java是传值还是传引用?请先不要看下面的内容,思考10秒后,在继续阅…

    2022年2月28日
    63
  • 八数码问题求解「建议收藏」

    八数码问题求解「建议收藏」(一)问题描述在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。该问题称八数码难题或者重排九宫问题。(二)问题分析八数码问题是个典型的状态图搜索问题。搜索方式有两种基本的方式,即树式搜索和线式搜索。搜索策略大体有盲…

    2022年7月26日
    7
  • 选择排序算法详解_八大排序算法图解

    选择排序算法详解_八大排序算法图解选择排序就是从待排序的元素中选择最小(最大)的元素,将其放在有序序列的相应位置,使这些元素构成有序序列。选择排序主要有两种:简单选择排序和堆排序。【简单选择排序】编写算法,要求使用简单选择排序算法对元素65、32、71、28、83、7、53、49进行从小到大排序。【算法思想】简单选择排序是一种简单的选择类排序算法,它的基本思想描述如下:假设待排序的元素有n个,在第一趟排序过程…

    2025年7月8日
    4
  • BS架构与CS架构的区别(最详细)「建议收藏」

    BS架构与CS架构的区别(最详细)「建议收藏」BS架构与CS架构的区别引言特点C/S系统结构B/S系统结构CS与BS的比较C/S与B/S区别:现状与趋势(转自知乎)引言C/S结构,即Client/Server(客户机/服务器)结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,可以充分利用两端硬件环境的优势。早期的软件系统多以此作为首选设计标准。B/S结构,即Browse…

    2022年10月17日
    4
  • ldap服务器是什么

    ldap服务器是什么LDAP服务器简单来说它是一种得到某些数据的快捷方式,同时LDAP服务器也是一个协议,它经常被用作集体的地址本使用,甚至可以做到更加庞大。它是一种特殊的数据库,与一般的数据库相比有很大的差距,LDAP服务器的读性与一般服务器相比更加优秀。同时LDAP服务器在查询上总了很多的优化,所以利用它可以快速查询出想要得到的结果,当然它也有缺陷,比如在更新方面,它会更新的很慢。LDAP服务器的目录有哪些优势…

    2022年5月14日
    65

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号