Vue2 - 数据响应式原理

目录

  • 1,总览
  • 2,Observer
  • 3,Dep
  • 4,Watcher
  • 5,Schedule

1,总览

vue2官网参考

在这里插入图片描述
简单介绍下上图流程:以 Data 为中心来说,

  1. Vue 会将传递给 Vue 实例的 data 选项(普通 js 对象),通过 Object.defineProperty 把这些 property 全部转为 getter/setter
  2. 当执行 render 函数时,会触发用到的响应式数据的 gettergetter 会进行依赖收集并放到 Watcher 中。
  3. 当修改被收集到 Watcher 中的响应式数据时,会触发 settersetter 会通知 Watcher 来重新执行 render 函数更新DOM树,同时再次进行第2步,形成闭环重复整个流程。

template 模板最终也会被编译为 render 函数执行。参考虚拟DOM树生成流程

响应式数据的目标:当对象本身或是属性发生变化时,会运行一些函数(最常见的是 render 函数)。

具体实现,vue 用到了几个核心部件。

  • Observer
  • Dep
  • Watcher
  • Schedule

2,Observer

目标:将传递给 Vue 实例的 data 选项(普通 js 对象)转化为响应式对象。

为了实现这点,Observer 把对象的每个属性通过 Object.defineProperty 转换为带有 getter/setter 的属性。这样当访问或修改这些属性时,vue 就可以做一些事情了。

在这里插入图片描述
Observer 是 Vue 内部的构造器,可以通过 Vue 提供的静态方法 Vue.observable(object) 间接使用该功能。

Vue.observable(object) 的使用场景参考这篇文章。

时间点:发生在 beforeCreate 之后,created之前。

具体实现:递归遍历所有属性,以完成深度的属性转换。

而由于只能遍历已有的属性,所以无法监测到将来动态添加或删除的属性。因为提供了 $set$delete 这2个实例方法。

对于数组,为了监听那些可能改变数组内容的方法,vue 更改了数组的隐式原型

vue 处理过后的数组 this.arr.__proto__上有7个方法可以被监听到。同时 this.arr.__proto__.__proto__ 指向真正的数组原型来正常使用数组的其他方法。
在这里插入图片描述

注意,直接修改数组的元素,是无法触发更新的。比如this.arr[0] = 1。但是修改数组中某一个元素对象的属性时,是可以监听到的。比如 this.arr[0].name = 'xxx'

总之,Observer 就是为了让一个对象属性的读取和赋值,内部数组变化等都可以被感知到。

3,Dep

作用和原理:

Vue会为响应式对象中的每个属性、对象本身、数组本身创建一个 Dep 实例(一个列表),每个 Dep 实例都可以做两件事:

  • 记录(收集)依赖:当读取响应式对象的某个属性时,它会进行依赖收集。
  • 派发更新:当改变某个属性时,它会派发更新。
function defineReactive(obj, key, val) {
  let Dep; // 依赖

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      // 被读取了,将这个依赖收集起来
      Dep.depend();
      return val;
    },
    set: (newVal) => {
      if (val === newVal) {
        return;
      }
      val = newVal;
      // 被改变了,派发更新
      Dep.notify();
    },
  });
}

在这里插入图片描述
举例:

<!-- 组件 -->
<template>
  <div>
    <div>{{ obj.a }}</div>
    <div>{{ arr }}</div>
    <button @click="count++">修改conut</button>
  </div>
</template>

<script>
// 会创建的 Dep 的元素:
export default {
  data() {
    return {
      obj: { // Dep
        a: 1, // Dep
        b: 2
      },
      arr: [1, 23, 4], // Dep
      count: 0
    };
  },
};
</script>
  • 因为模板中使用了 obj.a,所以 obj 自身和 a 属性都会创建 Dep
  • count 不会创建,是因为事件在渲染时不会运行。

有个问题,为什么要给对象自身也创建个 Dep,直接给用到的属性创建不可以吗?

不可以,因为直接修改对象(this.obj = xxx),或通过 this.$set(this.obj,"c", 3)this.$set(this.obj,"a") 增删属性时,都需要直接修改对象自身,才能完成响应式更新。

所以,最好一开始就定义好对象属性的初始值,来避免使用 this.$setthis.$delete 来触发对象自身的 Dep
因为使用 this.obj.a 直接触发属性 aDep 效率会更好。

对一个属性来说,会收集依赖的有3个可能的位置(因为都需要响应式更新或执行):

  • 模板,也就是 render() 中。
  • this.$watch() 中。
  • computed() 中。

4,Watcher

新的问题:Dep 是如何知道谁在用我的?换句话说,是谁触发的 getter 后执行的 Dep.depend()

比如,某个函数执行时用到了响应式数据 aa 怎么知道是哪个函数用的自己?

Vue的解决方式:Vue 不会直接执行函数,而是把函数交给一个叫 Watcher(一个对象)去执行。每个用到响应式数据的函数执行时,都会创建一个 Watcher,通过它来执行函数。
之后响应式数据变化时,Dep 会通知对应的 Watcher,去运行对应的函数来触发更新。

在这里插入图片描述

Watcher 大致原理

首先有一个全局变量。

  1. 在执行给它的函数之前,将它的 this 赋值给这个全局变量。
  2. 执行函数时,会触发响应式数据的 getter 后执行的 Dep.depend()
  3. Dep.depend() 的逻辑中,会检查这个全局变量,从而确定是哪个 Watcher
  4. 函数执行完后清空全局变量。

所以,对于一个组件实例来说,都至少对应一个 Watcher ,它记录的是该组件的 render 函数。
Watcher 首先会运行一次 render 来收集依赖,于是 render 函数中用到的响应式数据都会记录这个 Watcher
之后响应式数据变化,Dep 会通知这个 Watcher 来运行 render 函数触发更新,重新渲染页面同时再次收集当前的依赖。

打印组件的 this

在这里插入图片描述

Watcher 触发更新时,会进行对比新旧虚拟DOM树,完成对真实DOM的更新。具体原理参考diff 的原理

5,Schedule

新的问题又出现了,假如 render 函数中使用的响应式数据有多个 abcd,那这些数据都会记录 Watcher,之后一次性修改这4个时,render 函数就会执行4次,效率岂不是很低。

实际上,Watcher 在收到派发更新的通知后,不是立即执行对应的函数,而是把自己交给一个叫Schedule(调度器)的东西。

调度器维护一个队列,相同的 Watcher 仅会存在一次(类似 Set)。这些 Watcher 也不是立即执行,而是把需要执行的 Watcher 放到事件循环的微队列中(通过工具方法 nextTick )。

所以当响应式数据发生变化时,执行的 render 函数是异步的。


整体流程:

在这里插入图片描述


以上。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HackTheBox - Medium - Linux - Awkward

Awkward Awkward 是一款中等难度的机器&#xff0c;它突出显示了不会导致 RCE 的代码注入漏洞&#xff0c;而是 SSRF、LFI 和任意文件写入/追加漏洞。此外&#xff0c;该框还涉及通过不良的密码做法&#xff08;例如密码重用&#xff09;以及以纯文本形式存储密码来绕过身份验…

Python轴承故障诊断 (九)基于VMD+CNN-BiLSTM的故障分类

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断 (一)短时傅里叶变换STFT Python轴承故障诊断 (二)连续小波变换CWT_pyts 小波变换 故障-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD_轴承诊断 …

算法训练day60|单调栈part0

参考&#xff1a;代码随想录 84.柱状图中最大的矩形 要求当前柱形的左右两边第一个比他小的位置 对于高度为5的柱子&#xff08;index为2&#xff09; mid 他的左边第一个比他小的柱子为1&#xff0c;index为1 left 他的右边第一个比他小的柱子高度为2&#xff0c;index为4…

场景识别与词袋模型

目录 1. 任务要求2. 数据集3. 实现算法3.1 目标实现3.2 Tiny images representation3.3 SIFT特征词袋表示3.4 相关算法 4. 实验结果4.1 基础结果展示4.2 算法超参的影响4.2.1 Tiny images size4.2.2 Vocabulary size 4.3 其他结果 5. 源代码 1. 任务要求 输入&#xff1a;给定…

redis黑马点评项目启动指南(含mac m1pro | windows11 wsl2 ubuntu环境配置 持续更新中~)

redis黑马点评项目学习笔记 mac m1pro windows 含项目配置教学 mac M1pro环境配置windows11 wsl2 ubuntu 环境配置一.短信登录1. 1发送验证码1.2短信登录注册1.3登录校验拦截器补缺Cookie Session Token1.4基于redistoken认证实现短信登陆1.5完善token认证的刷新机制 2.商户查询…

Redis 给集合元素单独设置过期

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、场景 1.1 消费队列 1.2 Redis实现 二、常见的方案 2.1 为单独的 field 设置过期 2.2 设置整体过期时间 2.3 zset 结合 sc…

项目实战:数字孪生可视化大屏幕,实现生产过程实时监控

项目介绍 智慧工厂数据可视化系统&#xff0c;融合工业大数据、物联网、人工智能等各类信息技术&#xff0c;整合厂区现有信息系统的数据资源&#xff0c;实现数字孪生工厂、设备运维监测、智能管网监测、综合安防监测、便捷通行监测、能效管理监测、生产管理监测、仓储物流监…

【数据分析实战】冰雪大世界携程景区评价信息情感分析采集词云

文章目录 引言数据采集数据集展示数据预处理 数据分析评价总体情况分析本人浅薄分析 各游客人群占比分析本人浅薄分析 各评分雷达图本人浅薄分析 差评词云-可视化本人浅薄分析 好评词云-可视化本人浅薄分析 综合分析写在最后 今年冬天&#xff0c;哈尔滨冰雪旅游"杀疯了&q…

技术学习|CDA level I 业务分析方法

业务分析方法有三个主要构成部分&#xff1a;业务指标分析、业务模型分析及业务分析方法。 业务指标分析是发现业务问题的核心方法&#xff1a;用于通用指标和场景指标的计算及分析方法&#xff0c;以及指标体系的设计与应用方法。业务模型是从一系列业务行为中抽象出来的信息…

请你列出逻辑电路中的24种表达式

随着时代发展&#xff0c;数字电路的使用频率越来越高&#xff0c;完全不低于模拟电路&#xff0c;因此从事数字电路的工程师越来越多&#xff0c;如果你想成为一名优秀的数字工程师&#xff0c;一定要学会下面的逻辑电路表达式&#xff01; 1、基本逻辑运算与运算 (AND): A AN…

04 帧 Frame

文章目录 04 帧 Frame4.1 相机相关信息4.2 特征点提取4.2.1 特征点提取 ExtractORB()4.3 ORB-SLAM2对双目/RGBD特征点的预处理4.3.1 双目视差公式4.3.2 双目图像特征点匹配 ComputeStereoMatches()4.3.3 根据深度信息构造虚拟右目图像&#xff1a;ComputeStereoFromRGBD() 4.4 …

Python中的h5py包使用

h5py是一个非常强大的工具&#xff0c;可以用于存储和处理大量科学数据。它可以帮助我们提高数据处理的效率和可靠性。 目录 一、h5py1.1 特点1.2 主要功能1.3 常用场景 二、安装h5py三、示例代码3.1 运行结果 四、总结 一、h5py h5py是Python中的一个库&#xff0c;提供了对H…

JS函数实现数字转中文大写

JS函数实现数字转中文大写 1. 数字转字符,分割,去除空字符2. 遍历分割字符,替换为中文3. 增加四位数单位4. 处理零5. 拼接四位数据和单位 项目中,JS将万亿以下正整数转为中文大写 1. 数字转字符,分割,去除空字符 function toChineseNumber(num){const strs num.toString().re…

计算机组成原理 总线

总线 总线定义 总线 总线是一组能为多个部件分时共享的公共信息传送线路 总线的好处 早期计算机外部设备少时大多采用分散连接方式&#xff0c;不易实现随时增减外部设备 为了更好地解决I/O设备和主机之间连接的灵活性问题&#xff0c;计算机的结构从分散连接发展为总线连接 …

Mac环境下Parallels Desktop 19的安装和使用

为了后续构建漏洞靶场和渗透测试环境&#xff0c;我们需要提前准备好几套与宿主机隔离的工作环境&#xff08;Windows、Linux等&#xff09;&#xff0c;在Mac上最常用的就是Paralles Desktop&#xff08;PD&#xff09;工具了&#xff0c;当前最新版本为19。接下来介绍如何安装…

QT工具栏开始,退出

QT工具栏开始&#xff0c;退出 //初始化场景QMenuBar *bar menuBar();setMenuBar(bar);QMenu *startbar bar->addMenu("开始");QAction * quitAction startbar->addAction("退出");connect(quitAction , &QAction::triggered,[](){this->c…

Chromedriver 下载和安装指南

1. 确定Chrome浏览器版本 首先&#xff0c;在谷歌浏览器中找到当前版本信息。 打开“设置”&#xff0c;点击“关于谷歌”即可看到版本号。确保后续下载的Chromedriver版本与Chrome浏览器版本一致。或者直接跳转网页地址&#xff1a;chrome://settings/help 2. 下载Chromedri…

js逆向第12例:猿人学第5题js混淆-乱码增强

文章目录 那么`RM4hZBv0dDon443M=`是怎么来的?密钥怎么找加密数组怎么破解_0x4e96b4[_$pr]m=,f=时间戳是哪个?打开控制台查看数据接口 https://match.yuanrenxue.cn/api/match/5?page=2&m=1704439385499&f=1704439384000 利用postman测试接口请求,判断参数是否强…

机器学习 - 决策树

场景 之前有说过k近邻算法&#xff0c;k近邻算法是根据寻找最相似特征的邻居来解决分类问题。k近邻算法存在的问题是&#xff1a;不支持自我纠错&#xff0c;无法呈现数据格式&#xff0c;且吃性能。k近邻算法的决策过程并不可视化。对缺失数据的样本处理很不友好&#xff0c;…

C++ OpenGL 3D GameTutorial 1:Making the window with win32 API学习笔记

视频地址https://www.youtube.com/watch?vjHcz22MDPeE&listPLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg 一、入口函数 首先看入口函数main代码&#xff1a; #include<OGL3D/Game/OGame.h>int main() {OGame game;game.Run();return 0; } 这里交代个关于C语法的问题&#x…
最新文章