vue2响应式原理----发布订阅模式

news2024/11/23 18:49:49

很多人感觉vue2的响应式其实用到了观察者+发布订阅。我们先来看一下简单的发布订阅的代码:

// 调度中心
class Dep {
    static subscribes = {}
    // 订阅所有需求
    static subscribe (key, demand) {
      // 对需求分类收集
      if (!Dep.subscribes[key]) Dep.subscribes[key] = []
      Dep.subscribes[key].push(demand)
    }
    // 对所有订阅者发布通知
    static publish (key, age) {
      if (!Dep.subscribes[key]) return
      for (const demand of Dep.subscribes[key]) {
        demand(age)
      }
    }
  }
  // 找对象的猎手类
  class Watcher {
    constructor (name, age) {
      this.name = name // 名字
      this.age = age // 年龄
    }
    // 订阅,由调度中心将猎手需求分类并存放到全局
    subscribe (key, demand) {
      Dep.subscribe(key, demand)
    }
    // 发布,由调度中心将同分类下的需求全部触发
    publish (key, age) {
      Dep.publish(key, age)
    }
  }
  // 猎手注册
  const aa = new Watcher('aa', 18)
  const bb = new Watcher('bb', 20)
  // 猎手订阅自己感兴趣的人
  aa.subscribe('key', function (age) {
    if (age === aa.age) console.log(`我是aa,我们都是${age}`)
    else console.log(`我是aa,我们年龄不同`)
  })
  bb.subscribe('key', function (age) {
    if (age === bb.age) console.log(`我是bb,我们都是${age}`)
    else console.log(`我是bb,我们年龄不同`)
  })
  // 红娘注册
  const red = new Watcher('red', 35)
  // 红娘发布信息
  red.publish('key', 20)
  // 我是aa,我们年龄不同
  // 我是bb,我们都是20

从上面中发现一个重要的点,发布者和订阅者是根据key值来区分的,然后通过消息中心来中转的,他们家是是实现不知道对方是谁。
而观察者模式中观察者是一开始就知道自己观察的是谁。

上面其实就是简易版的vue原理中发布订阅那段,我们接下来看完整过程。

Vue2 的响应式

  1. 创建一个 Observer 对象,它的主要作用是给对象的每个属性添加 getter 和 setter 方法。
  2. 在 getter 和 setter 方法中分别进行依赖的收集和派发更新。
  3. 创建 Watcher 对象,用于监听数据的变化,当数据发生任何变化时,Watcher 对象会触发自身的回调函数。
  4. 在模板解析阶段,对模板中使用到的数据进行依赖的收集,即收集 Watcher 对象。
  5. 当数据发生变化时,Observer 对象会通知 Dep 对象调用 Watcher 对象的回调函数进行更新操作,即派发更新。
  6. 更新完毕后,Vue2 会进行视图的重新渲染,从而实现响应式。

下面是一个基于 Object.defineProperty 实现响应式的示例,仅供参考:

function observe(obj) {
  if (!obj || typeof obj !== 'object') {
    return;
  }
  Object.keys(obj).forEach(key => {
    // 尝试递归处理
    observe(obj[key]);
    let val = obj[key];
    const dep = new Dep(); // 新建一个依赖
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.depend(); // 收集依赖
        }
        return val;
      },
      set(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        dep.notify(); // 派发更新
      }
    });
  });
}
 
// 依赖类
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    const index = this.subs.indexOf(sub);
    if (index !== -1) {
      this.subs.splice(index, 1);
    }
  }
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}
 
Dep.target = null;
 
// 观察者类
class Watcher {
  constructor(vm, expOrFn, callback) {
    this.vm = vm;
    this.getter = parsePath(expOrFn);
    this.callback = callback;
    this.value = this.get(); // 初始化,触发依赖
  }
  get() {
    Dep.target = this; // 设置当前依赖
    const value = this.getter.call(this.vm, this.vm); // 触发 getter
    Dep.target = null; // 清除当前依赖
    return value;
  }
  addDep(dep) {
    dep.addSub(this);
  }
  update() {
    const oldValue = this.value;
    this.value = this.get(); // 重新获取
    this.callback.call(this.vm, this.value, oldValue); // 触发回调
  }
}
 
// 解析路径
function parsePath(expOrFn) {
  if (typeof expOrFn === 'function') {
    return expOrFn;
  }
  const segments = expOrFn.split('.');
  return function(obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) {
        return;
      }
      obj = obj[segments[i]];
    }
    return obj;
  };
}
 
// 测试
const obj = { foo: 'foo', bar: { a: 1 } };
observe(obj);
new Watcher(obj, 'foo', (val, oldVal) => {
  console.log(`foo changed from ${oldVal} to ${val}`);
});
new Watcher(obj, 'bar.a', (val, oldVal) => {
  console.log(`bar.a changed from ${oldVal} to ${val}`);
});
 
obj.foo = 'FOO'; // 输出 `foo changed from foo to FOO`
obj.bar.a = 2; // 输出 `bar.a changed from 1 to 2`

以上代码中,函数 observe 用于递归遍历对象属性,把其进行劫持,包括收集依赖和派发更新;类 Dep 代表一个依赖,其中 addSub 用于添加订阅者实例,removeSub 用于移除订阅者实例,depend 用于收集依赖,即把当前依赖加到对应的订阅者中,notify 用于派发更新,即遍历所有订阅者,并触发其回调函数。类 Watcher 则代表一个订阅者,其中 getter 用于获取数据,callback 用于回调函数,addDep 用于添加依赖,即把当前订阅者添加到对应的依赖中,update 用于更新值,并触发相应的回调函数,如有必要。函数 parsePath 则用于解析路径字符串,返回对应属性的值。

例子中我们对对象 obj 进行了劫持,同时创建了两个观察者,分别对应 foo 和 bar.a 两个属性。当其中任意一个属性的值发生变化时,其对应的依赖都会被更新,从而触发其绑定的观订阅者的回调函数。

简单来说,在 Vue2 响应式系统中,当数据发生改变时,会触发 get 和 set 方法,get 方法会收集所有依赖该数据的 Watcher 对象,set 方法会通知 Dep 对象触发所有 Watcher 对象的回调函数进行更新。如此循环,实现了数据的响应式。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1591922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用腾讯云服务器如何搭建网站?新手建站教程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com整理使用腾讯云服务器建站教程&#xff0c;…

前端下载url文件(解决PDF, 图片自动在浏览器打开)

常规下载方法&#xff1a; /* 方法1 */ window.open(下载url地址, _blank)/* 方法2 */ const link document.createElement("a"); link.download true; link.href 下载url地址; link.click(); document.body.removeChild(link);pdf文件默认在浏览器中展示解决方案…

Linux:Zabbix + Grafana10.4.2(3)

1.部署zabbix 下面这篇文章写了详细的部署zabbix过程 &#xff0c;使用的centos9系统 Linux&#xff1a;部署搭建zabbix6&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/137426966?spm1001.2014.3001.5501下面这篇文章使用的是centos7…

RTSP/Onvif安防视频EasyNVR平台 vs.多协议接入视频汇聚EasyCVR平台:设备分组的区别

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

二叉树练习day.8

235.二叉搜索树的最近公共祖先 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共…

30万的源码和300的源码有什么区别?

源码的质量很大程度上取决于其来源、开发者的技术水平和项目的具体需求。有些源码可能确实存在一些问题&#xff0c;比如代码结构混乱、注释不清晰、性能不佳等。 而价高优秀的源码都采用了高效的数据结构和算法&#xff0c;代码结构清晰&#xff0c;逻辑严谨&#xff0c;具有良…

ubuntu22下使用vscode调试redis7源码环境搭建

ubuntu22下使用vscode调试redis7源码环境搭建 ##vscode launch.json配置文件 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0&…

【神经网络与深度学习】循环神经网络基础

tokenization tokenization&#xff1a;分词 每一个词语都是token 分词方法&#xff1a;转为单个词、转为多个词语 N-gram表示法 准备词语特征的方法 &#xff08;把连续的N个词作为特征&#xff09; 如 ”我爱你“——>[我&#xff0c;爱&#xff0c;你] 2-gram——[[我…

游戏服务器DDOS克星-抗D盾(游戏盾)

随着网络游戏市场的不断扩大和发展&#xff0c;游戏服务器遭受DDOS攻击的频率也在逐年增加。DDOS攻击的主要目的是使游戏服务器瘫痪&#xff0c;使得游戏无法正常进行&#xff0c;导致游戏运营商巨额损失。鉴于此&#xff0c;针对游戏服务器的防DDOS攻击技术德迅云安全自主研发…

Ubuntu 22上安装Anaconda3。下载、安装、验证详细教程

在Ubuntu 22上安装Anaconda3&#xff0c;你可以遵循以下步骤&#xff1a; 更新系统存储库&#xff1a; 打开终端并运行以下命令来更新系统存储库&#xff1a; sudo apt update安装curl包&#xff1a; 下载Anaconda安装脚本通常需要使用curl工具。如果系统中没有安装curl&#x…

【Python函数和类4/6】递归与匿名函数

目录 目标 匿名函数 多个形参 匿名函数的局限性 递归 语言例子 数学例子 递归的实现 递归代码 练习 总结 目标 在之前的博客中&#xff0c;我们学习了定义函数、调用函数以及设置函数的参数。在今天&#xff0c;我们会补充函数的两个常见的知识点&#xff0c;一个是匿…

前端css笔记(pink老师)

css css书写顺序 自适应屏幕 html { width: 100%; height: 100%; display: table; } body { display: table-cell; } 用了这个方法以后&#xff0c;如果希望页面内的盒子也适应屏幕大小&#xff0c;则使用以下方法&#xff0c;会根据父亲的宽高计算出该盒子的宽高 width:xx%; …

策略模式(知识点)——设计模式学习笔记

文章目录 0 概念1 使用场景2 优缺点2.1 优点2.2 缺点 3 实现方式4 和其他模式的区别5 具体例子实现5.1 实现代码 0 概念 定义&#xff1a;定义一个算法族&#xff0c;并分别封装起来。策略让算法的变化独立于它的客户&#xff08;这样就可在不修改上下文代码或其他策略的情况下…

死锁的成因, 和解决方案.

死锁 死锁就是两个或两个以上线程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信而造成的一种阻塞的现象&#xff0c;若无外力作用&#xff0c;它们都将无法推进下去。 成因 1.一个线程一把锁 一个线程&#xff0c;对同一个对象&#xff0c;重复加锁两次&#xff0c;如…

相机参数的意义

相机标定的意义&#xff1a; 相机标定&#xff1a;使用带有pattern的标定板来求解相机参数的过程&#xff1b;用一个简化的数学模型来代表复杂的三维到二维的成像过程&#xff1b;相机参数包括&#xff1a;相机内参&#xff08;焦距等&#xff09;&#xff0c;外参&#xff08…

⑤-1 学习PID--什么是PID

​ PID 算法可以用于温度控制、水位控制、飞行姿态控制等领域。后面我们通过PID 控制电机进行说明。 自动控制系统 在直流有刷电机的基础驱动中&#xff0c;如果电机负载不变&#xff0c;我们只要设置固定的占空比&#xff08;电压&#xff09;&#xff0c;电机的速度就会稳定在…

Solana 上创建自己的 SLPToken:简明指南

Solana 定义 Solana 是由 Solana Labs 创建的区块链平台&#xff0c;旨在提供高吞吐量和低延迟的去中心化应用&#xff08;DApps&#xff09;开发环境。它采用一系列创新技术&#xff0c;如 PoH&#xff08;Proof of History&#xff09;共识机制和 Tower BFT&#xff08;BFT …

好用的企业知识管理SaaS产品推荐来啦,小白必看!

知识管理在企业运营中扮演了重要角色&#xff0c;特别是在现代化办公环境下&#xff0c;一个高效卓越的知识管理系统是企业提高生产力、促进创新和保持竞争力的关键。SaaS(Software as a Service) 我们通常称之为“软件即服务”&#xff0c;为企业提供了灵活、高效和划算的知识…

每日一题(力扣)---插入区间

官方网址&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals按照 st…

开源免费AI引擎:智能合同审查技术的应用与优势

随着数字化转型的加速&#xff0c;合同作为商业活动中的重要法律文件&#xff0c;其审查和管理变得越来越重要。传统的合同审查方式耗时且容易出错&#xff0c;而智能AI合同审查技术的引入&#xff0c;为这一领域带来了革命性的变化。本文将探讨智能AI合同审查技术的应用和优势…