Vue2 - 数据响应式原理

news2024/9/24 19:23:03

目录

  • 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

在这里插入图片描述

5,Schedule

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

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

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

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


整体流程:

在这里插入图片描述


以上。

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

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

相关文章

SSM养老院综合服务系统----计算机毕业设计

项目介绍 该项目为后台管理项目&#xff0c;分为管理员与护工两种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,个人资料密码管理,用户管理,床位类型管理,床位管理,护工管理,老人管理,咨询登记管理,预约登记管理,老人健康信息管理,费用管理等功能。 护…

【LeetCode每日一题】2487. 从链表中移除节点(调用栈+递归+翻转链表)

2024-1-3 文章目录 [2487. 从链表中移除节点](https://leetcode.cn/problems/remove-nodes-from-linked-list/)方法一&#xff1a;调用栈方法二&#xff1a;递归方法三&#xff1a;翻转链表 2487. 从链表中移除节点 方法一&#xff1a;调用栈 1.将所有节点按顺序压入栈中 2.从…

浅谈接口自动化测试

昨晚在某个测试交流群&#xff0c;听了一个测试老司机分享接口自动化测试的内容&#xff0c;对接口自动化有了更深的一些认识&#xff0c;也为接下来公司的接口自动化实施&#xff0c;提供了更多的思路。 这篇博客&#xff0c;就说说功能测试到接口自动化的进阶&#xff0c;以及…

商品推荐系统+可视化+2种协同过滤推荐算法 Django框架 大数据毕业设计(附源码+论文)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

【论文笔记】An Extractive-and-Abstractive Framework for Source Code Summarization

An Extractive-and-Abstractive Framework for Source Code Summarization 1. Introduction2. Model2.1 Overview2.2 Training of EACS2.2.1 Part i : Training of Extractor2.2.2 Part ii : Training of Abstracter 3. Evaluation 1. Introduction 代码摘要可以细分为抽取式代…

眼镜店系统管理软件,眼镜店配镜视力检查顾客资料管理系统

一、软件程序问答 1、这个软件在配镜的时候可以开配镜处方吗&#xff0c;可以打印出来吗&#xff1f; 如上图&#xff0c;以 佳易王眼镜店配镜顾客信息管理系统为例说明&#xff1a; 点击软件中的 配镜处方按钮&#xff0c;填写配镜相关信息&#xff0c;即可打印&#xff0c;…

Spring系列学习六、深入Spring AOP——揭开代理的神秘面纱

深入Spring AOP——揭开代理的神秘面纱 一、动态代理的实现原理二、CGLIB字节码增强的实现原理三、结语 上一章节&#xff0c;我们体验了Spring AOP强大的能力的同时&#xff0c;是不是也想弄明白&#xff0c;它是怎么原理是什么呢&#xff1f;如果自己要做一个类似的框架&…

SQL Server注入之攻防技战法

那天下着很大的雨&#xff0c;母亲从城里走回来的时候&#xff0c;浑身就是一个泥人&#xff0c;那一刻我就知道我没有别的选择了 1.Mssql报错注入 0.判断数据库类型 1.爆当前用户名 2.爆版本 3.爆服务器名 4.判断数据库个数 5.获取全部数据库 语句只适合>2005 爆当前数据…

旧电脑搭建NAS

旧电脑可以搭建NAS吗&#xff1f; 可以&#xff01; 性能好吗&#xff1f; 完全没问题&#xff01; 简单吗&#xff1f; 轻松上手&#xff01; 怎吗搭建&#xff1f; 这里&#xff1a;用旧电脑搭建NAS在您的家庭中&#xff0c;通过将旧 PC 转变为NAS服务器&#xff0c;您…

Winform中使用Fleck实现Websocket服务端并读取SQLite数据库中数据定时循环群发消息

场景 Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中&#xff1a; Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中-CSDN博客 Winform中操作Sqlite数据增删改查、程序启动时执行创建表初始化操作&#xff1a; Wi…

BLE Mesh蓝牙组网技术详细解析之Access Layer访问层(六)

目录 一、什么是BLE Mesh Access Layer访问层&#xff1f; 二、Access payload 2.1 Opcode 三、Access layer behavior 3.1 Access layer发送消息的流程 3.2 Access layer接收消息的流程 3.3 Unacknowledged and acknowledged messages 3.3.1 Unacknowledged message …

轻松上手:Postman Interceptor 插件使用指南

什么是 Postman&#xff1f; Postman 是一种用于测试和开发 API 的工具&#xff0c;让开发者可以轻松地构建、发送、调试 HTTP 请求&#xff0c;并检查响应结果。通过Postman&#xff0c;开发者可以在不编写代码的情况下快速测试 API 的正确性和可靠性。Postman 还支持协作和自…

ubuntu18.04安装MySQL

1.安装mysql服务器端 sudo apt-get -y install mysql-server&#xff08;18.04/20.04不会提示输入密码&#xff0c;默认是没有密码&#xff09; 2.安装mysql客户端 sudo apt-get -y install mysql-client3.安装mysql模块 sudo apt-get -y install libmysqlclient-dev4.验证是…

融资项目——全局统一日志说明

通过日志可以查看程序的运行信息和异常信息等&#xff0c;便于维护。日志级别分为TRACE、DEBUG、INFO、WARN、ERROR级别&#xff0c;越往后打印的日志信息越少&#xff0c;如ERROR 级别只会在程序运行出错时才会打印日志。可在application.properties中设置日志级别。 logging…

Python+OpenGL绘制3D模型(七)制作3dsmax导出插件

系列文章 一、逆向工程 Sketchup 逆向工程&#xff08;一&#xff09;破解.skp文件数据结构 Sketchup 逆向工程&#xff08;二&#xff09;分析三维模型数据结构 Sketchup 逆向工程&#xff08;三&#xff09;软件逆向工程从何处入手 Sketchup 逆向工程&#xff08;四&#xf…

最新Tomcat下载安装详细教程

Tomcat下载安装教程 Tomcat简介Tomcat下载tomcat安装验证安装是否成功 Tomcat简介 Tomcat是什么&#xff1f; Tomcat是web容器。你在做web项目时&#xff0c;多数需要http协议&#xff0c;也就是基于请求和响应&#xff0c;比如你在百度输入一行内容搜索&#xff0c;那么百度服…

一文讲清数据资产入表实操

《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》已发布一年&#xff0c;数据资产化和入表已成为2023年的热门话题&#xff0c;随着2023年底国家数据局吹风《"数据要素x"三年行动计划&#xff08;2024-2026年&#xff09;》即将发布&#xff0c;这…

Java_IO流(字节流)

一、IO流&#xff08;字节流&#xff09; 1.1 IO流概述 在前面已经学习过File类。知道File只能操作文件&#xff0c;但是不能操作文件中的内容。我们也学习了字符集&#xff0c;不同的字符集存字符数据的原理是不一样的。有了前面两个知识的基础&#xff0c;接下来我们再学习…

Git(3):Git环境常用命令

1 获取本地仓库 要使用Git对我们的代码进行版本控制&#xff0c;首先需要获得本地仓库 &#xff08;1&#xff09;在电脑的任意位置创建一个空目录&#xff08;例如test&#xff09;作为我们的本地Git仓库 &#xff08;2&#xff09;进入这个目录中&#xff0c;点击右键打开…