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 函数时,会触发用到的响应式数据的 getter,getter 会进行依赖收集并放到 Watcher 中。
  3. 当修改被收集到 Watcher 中的响应式数据时,会触发 setter,setter 会通知 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: { // Depa: 1, // Depb: 2},arr: [1, 23, 4], // Depcount: 0};},
};
</script>
  • 因为模板中使用了 obj.a,所以 obj 自身和 a 属性都会创建 Dep。
  • count 不会创建,是因为count其实并没有在模板中使用,而事件在渲染时不会运行。

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

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

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

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

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

4,Watcher

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

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

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 函数中使用的响应式数据有多个 a,b,c,d,那这些数据都会记录 Watcher,之后一次性修改这4个时,render 函数就会执行4次,效率岂不是很低。

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

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

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


整体流程:

在这里插入图片描述


以上。

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

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

相关文章

吴飞教授 人工智能 模型与算法 启发式搜索课件发散分析

一、文章介绍 本文是针对吴飞教授在MOOC课程 &#xff1a;《人工智能&#xff1a;模型与算法》 2.1节 启发式搜索的课前发散 在课程2.1节 启发式搜索章节中&#xff0c;吴飞教授以如何计算城市地图两点之间最短路径为例&#xff0c;重点讲授了贪婪最佳优先搜索和A*搜索算法&a…

Cadence Editor 关于画PCB相关内容

目录 一 新建PCB文件 二 指定封装库 三 导入网表 四 放置器件 五 绘制板框 六 精准定位 七 原理图与PCB的交互 八 飞线设置 九 层管理 布局布线阶段需要显示的层 十 器件位置相关 1 器件选取的基准点 2 旋转 3 对齐 4 把器件移动到底层或顶层 5 锁定与解锁 6…

媒体捕捉-iOS自定义二维码扫描功能

引言 随着iOS 7引入AV Foundation框架&#xff0c;二维码扫描功能已经成为iOS应用程序中不可或缺的一部分。现今&#xff0c;几乎每个应用都充分利用这一功能&#xff0c;为用户提供了诸如扫码登录、扫码填充等丰富多彩的便捷体验。这项技术不仅丰富了应用功能&#xff0c;也为…

20240106 SQL基础50题打卡

20240106 SQL基础50题打卡 1164. 指定日期的产品价格 产品数据表: Products ------------------------ | Column Name | Type | ------------------------ | product_id | int | | new_price | int | | change_date | date | ---------------------…

20240104----重返学习-关于`npm --legacy-peer-deps`与`npm --force`升级版本

记–legacy-peer-deps与–force的了解与思考 之前还是旧的版本: "bpmn-js": "^7.3.1","bpmn-js-properties-panel": "^0.37.2","houtaroy-bpmn-js-properties-panel-activiti": "^0.0.1",使用npm install bpmn-js…

力扣(leetcode)第392题判断子序列(Python)

392.判断子序列 题目链接&#xff1a;392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;…

pyfolio工具结合backtrader分析量化策略组合,附源码+问题分析

pyfolio可以分析backtrader的策略&#xff0c;并生成一系列好看的图表&#xff0c;但是由于pyfolio直接install的稳定版有缺陷&#xff0c;开发版也存在诸多问题&#xff0c;使用的依赖版本都偏低&#xff0c;试用了一下之后还是更推荐quantstats。 1、安装依赖 pip install …

车辆运动学方程推导和代码实现

文章目录 1. 运动学方程2. 模型实现 1. 运动学方程 自行车模型&#xff08;Bicycle Model&#xff09;是车辆数字化模型中最常见的一种运动学模型。其除了可以反映车辆的一些基础特性外&#xff0c;更重要的是简单易用。通常情况下我们会把车辆模型简化为二自由度的自行车模型…

游戏Lua调用01.lua的编译及测试

一、lua库下载与编译 进入lua官网 Lua: version history 找到lua5.1 选择lua5.1是因为大部分游戏使用的都是lua5.1的库&#xff0c;也可以选择高版本&#xff0c;影响不大 下载完了后使用vs建立一个静态库或者动态库的工程 这里以动态库为例子&#xff0c;静态库也是一样的…

flink table view datastream互转

case class outer(f1:String,f2:Inner) case class outerV1(f1:String,f2:Inner,f3:Int) case class Inner(f3:String,f4:Int) 测试代码 package com.yy.table.convertimport org.apache.flink.streaming.api.scala.StreamExecutionEnvironment import org.apache.flink.tabl…

w18认证崩溃之暴力破解DVWA

一、实验环境 攻击工具&#xff1a;burpsuite2021.12 靶场&#xff1a;DVWA二、实验目的 演示暴破DVWA的medium和high两个级别&#xff0c;low级别请查看w18认证崩溃之暴力破解4种攻击模式 三、实验步骤 1.设置靶场medium级别 2.开启谷歌代理插件&#xff0c;开启bp拦截&…

AI教我学编程之AI自刀

AI教我学编程系列学习第二课 — C#变量类型 上节回顾知识梳理C#基本变量类型 对话AI分歧产生本段总结 它说得对吗&#xff1f;我随即发问经典AI自刀他来了 总结 上节回顾 在上一节中我们发现&#xff0c;AI工具似乎还不能达到教学的水平&#xff0c;所以在本节中&#xff0c;…

数据在内存中的存储方式

前言&#xff1a; 期末临近&#xff0c;继续复习&#xff01; 今天要复习的内容是数据在内存中的存储&#xff0c;主要是整型与浮点两种&#xff0c;还有大小端的介绍。 提出问题 打印结果是255 -1 为什么&#xff1f; 首先我们要知道数据都是以二进制的形式存…

Spring Framework和SpringBoot的区别

目录 一、前言 二、什么是Spring 三、什么是Spring Framework 四、什么是SpringBoot 五、使用Spring Framework构建工程 六、使用SpringBoot构建工程 七、总结 一、前言 作为Java程序员&#xff0c;我们都听说过Spring&#xff0c;也都使用过Spring的相关产品&#xff0…

uni-app 前后端调用实例 基于Springboot 详情页实现

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

mysql进阶-重构表

目录 1. 原因 2. 如何重构表呢&#xff1f; 2.1 命令1&#xff1a; 2.2 命令2&#xff1a; 2.3 命令3&#xff1a; 1. 原因 正常的业务开发&#xff0c;为什么需要重构表呢&#xff1f; 原因1&#xff1a;某张表存在大量的新增和删除操作&#xff0c;导致表经历过大量的…

JavaScript异常处理实战

前言 之前在对公司的前端代码脚本错误进行排查&#xff0c;试图降低 JS Error 的错误量&#xff0c;结合自己之前的经验对这方面内容进行了实践并总结&#xff0c;下面就此谈谈我对前端代码异常监控的一些见解。 本文大致围绕下面几点展开讨论&#xff1a; JS 处理异常的方式…

Keil 5 ARMCC编译错误和警告解释大全(2) 序列号1000-2000

1001&#xff1a;由 using 声明指定的类成员必须在直接基类中可见 1003&#xff1a;Sun 模式与 cfront 模式不兼容 1004&#xff1a;严格模式与 Sun 模式不兼容 1005&#xff1a;只有在编译 C 时才允许使用太阳模式 1006&#xff1a; 模板模板参数不能与其模板参数之一同名…

解决 Postman 报错问题:一份综合指南

Postman 是一个流行的 API 测试工具&#xff0c;它可以帮助开发者和测试人员快速地创建和发送各种 HTTP 请求&#xff0c;并查看响应结果。但是&#xff0c;在使用 Postman 的过程中&#xff0c;有时候会遇到一些报错或异常情况&#xff0c;影响了正常的测试流程。本文将介绍一…

图像分割-Grabcut法

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的C#版本请访问&#xff1a;图像分割-Grabcut法(C#)-CSDN博客 GrabCut是一种基于图像分割的技术&#xff0c;它可以用于将图像…