vue2和vue 3 的响应式原理

vue 3 响应式原理

在 Vue 3 中,响应式系统的核心是使用了 ES6 的 Proxy 对象来实现对数据的拦截和响应式更新。

简单的 Proxy 示例:

const data = { count: 0 };
const handler = {get(target, key, receiver) {// 当访问属性时触发track(target, key);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {// 当设置属性时触发const result = Reflect.set(target, key, value, receiver);trigger(target, key);return result;}
};
​
const reactiveData = new Proxy(data, handler);
const proxy = new Proxy(target, handler);
​
console.log(proxy.message); // "Getting property message" -> "Hello, world!"
proxy.message = "Hello, Vue 3!"; // "Setting property message to Hello, Vue 3!"

在上面的代码中,handler 对象定义了两个拦截器方法:getset。这两个方法分别用于拦截属性的读取和设置操作。

  • get 方法在属性被访问时触发。它接收三个参数:目标对象(target)、属性名(key)和代理对象(receiver)。在 get 方法中,你可以执行一些操作,比如追踪依赖(track),然后返回属性的值。

  • set 方法在属性被设置时触发。它接收四个参数:目标对象(target)、属性名(key)、新值(value)和代理对象(receiver)。在 set 方法中,你可以执行一些操作,比如触发依赖更新(trigger),然后返回一个布尔值表示设置操作是否成功。

在 Vue 3 中,Proxy 被用来实现响应式数据追踪和依赖收集。主要通过 reactiveref API 实现。

Reactive API 示例

import { reactive } from 'vue';
​
const state = reactive({count: 0
});
​
state.count++; // 这将触发 Vue 的响应式系统

Ref API 示例:

import { ref } from 'vue';
​
const count = ref(0);
​
count.value++; // 这也将触发 Vue 的响应式系统

响应式系统的工作原理

  1. 依赖收集:当响应式对象的属性被访问时,Proxyget 拦截器会触发依赖收集,将依赖(如组件或计算属性)记录下来。

  2. 触发更新:当响应式对象的属性被修改时,Proxyset 拦截器会触发通知,所有依赖于该属性的组件或计算属性都会重新计算或重新渲染。

简化的响应式系统示例:

import { reactive, effect } from 'vue';
const state = reactive({ count: 0 });
​
effect(() => {console.log(`Count is: ${state.count}`);
});
​
state.count++;
// 当 state.count 被修改时,上面的 effect 会重新执行,输出 "Count is: 1"

vue2 的响应式原理

Object.defineProperty 是 ES5 引入的一个方法,用于直接在一个对象上定义或修改一个属性,同时可以指定该属性的特性,如是否可枚举、是否可配置、是否可写等。

当你访问一个已经被 Vue 转换过的属性时,Vue 会将这个属性标记为“依赖”,然后当这个属性被修改时,Vue 会通知所有依赖于这个属性的地方进行更新。这也就实现了 Vue 的响应式系统。当你在模板中使用了一个 data 对象的属性时,比如 {{ message }},Vue 会在内部追踪 message 属性的依赖关系,当 message 发生变化时,Vue 会自动更新相关的视图。

定义响应式属性:Vue 定义响应式属性的核心逻辑如下:

function defineReactive(obj, key, val) {// 创建一个依赖管理器(Dep),用于收集和通知依赖const dep = new Dep();// 获取对象属性的当前描述符let property = Object.getOwnPropertyDescriptor(obj, key);
​// 如果不可配置,则直接返回if (property && property.configurable === false) {return;}// 缓存属性的 getter 和 setter(如果存在)const getter = property && property.get;const setter = property && property.set;
​// 使用 Object.defineProperty 重新定义属性Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {// 获取值const value = getter ? getter.call(obj) : val;// 如果存在当前依赖目标,则添加依赖if (Dep.target) {dep.depend();}return value;},set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : val;// 如果新值与旧值相同,则不触发更新if (newVal === value || (newVal !== newVal && value !== value)) {return;}// 如果存在 setter,则调用 setter,否则直接设置值if (setter) {setter.call(obj, newVal);} else {val = newVal;}// 通知所有依赖进行更新dep.notify();}});
}
  1. 依赖管理器 (Dep):这是一个简单的依赖管理器,用于收集依赖并在数据变化时通知它们:

let uid = 0;
​
class Dep {constructor() {this.id = uid++;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() {const subs = this.subs.slice();for (let i = 0; i < subs.length; i++) {subs[i].update();}}
}
Dep.target = null;

假设我们有一个 Vue 实例,其 data 对象中有一个属性 message

var vm = new Vue({el: '#app',data: {message: 'Hello Vue!'}
});

当你在 Vue 实例的模板中使用 {{ message }} 时,Vue 会创建一个“Watcher”来追踪这个属性的依赖:

watcher = new Watcher(vm, function updateComponent() {console.log(vm.message); // 读取 message 属性会触发 getter
});

当你修改 vm.message 的值时,setter 会被触发,并且 dep.notify() 会通知所有依赖于 message 的 watcher 更新,从而触发视图更新。

vm.message = 'Hello World!'; // 触发 setter,调用 dep.notify(),视图更新

Object.defineProperty与 proxy特点:

  • 局限性Object.defineProperty 只能对对象的属性进行拦截,不能拦截对象的新增属性、删除属性等操作。

  • 性能:在处理大量属性时,Object.defineProperty 的性能可能不如 Proxy,因为 Object.defineProperty 需要为每个属性单独设置拦截器。

  • 兼容性Object.defineProperty 在旧版浏览器中可能不被支持,而 Proxy 需要 ES6 环境。

    object.defineProperty 示例

    const obj = {};
    Object.defineProperty(obj, 'name', {get() {console.log('访问 name 属性');return '张三';},set(value) {console.log('设置 name 属性', value);}
    });
    ​
    console.log(obj.name); // 输出 '访问 name 属性' 和 '张三'
    obj.name = '李四'; // 输出 '设置 name 属性' 和 '李四'
    ​

    proxy 示例

    const handler = {get(target, propKey, receiver) {console.log(`访问 ${propKey} 属性`);return Reflect.get(target, propKey, receiver);},set(target, propKey, value, receiver) {console.log(`设置 ${propKey} 属性为 ${value}`);return Reflect.set(target, propKey, value, receiver);}
    };
    const proxy = new Proxy({}, handler);
    ​
    proxy.name = '王五'; // 输出 '设置 name 属性为 王五'
    console.log(proxy.name); // 输出 '访问 name 属性' 和 '王五'

需要注意的点:

1、Object.defineProperty 有一些限制,它只能监听对象的属性,并且需要遍历对象的属性来进行转换,这意味着在 Vue 2 中无法监听数组的变化。因此,在 Vue 2 中对数组的变化需要通过特定的方法来进行处理,比如使用 pushpop 等方法,或者直接使用 Vue.set 方法。

2、因此vue2 可能出现的一个问题,数据更新视图未更新的情况。但是vue2 推出了this.$set(target,key,修改后的值)。在vue3中使用了proxy 代理就很好的解决了这个问题。

3、异步更新问题:有些情况下,如果数据的变化发生在 JavaScript 执行栈之外(比如在定时器回调、Promise 回调中),Vue 可能无法立即捕获到数据的变化。这时可以使用 this.$nextTick() 来确保在 DOM 更新之后再执行一段代码。

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

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

相关文章

几款让你怦然心动的神奇工具——搜嗖工具箱

alteredqualia AlteredQualia 脑洞爆炸器网站&#xff0c;不得不说这是一个神奇的网站&#xff0c;在这个网站上你可以实现不可思议的各种操作&#xff0c;让我们对网站有了新的认知&#xff0c;因为它告诉你不是所有有趣的网站都那么花哨&#xff0c;有些网站看着外形平淡无奇…

Javascript - 请问可以new一个箭头函数吗?

&#x1f690;new操作符的步骤 在JavaScript中&#xff0c;new 操作符用于创建一个对象实例&#xff0c;具体来说&#xff0c;它会执行以下几步操作&#xff1a; 创建一个新对象&#xff1a; 创建一个新的空对象&#xff0c;且这个对象的 __proto__ 属性会被设置为构造函数的 …

LabVIEW结构体内部缺陷振动检测

结构体内部缺陷会改变其振动特性&#xff0c;通过振动分析可以检测并定位这些缺陷。本文详细分析内部缺陷对振动的影响&#xff0c;从频谱分析、时域分析和模态分析等多角度探讨基于LabVIEW的检测方法&#xff0c;提供实施步骤和注意事项&#xff0c;帮助工程师有效利用LabVIEW…

如何解决跨境传输常见的安全及效率问题?

在当今全球化的商业版图中&#xff0c;企业为了拓展国际市场和增强竞争力&#xff0c;跨境传输数据已成为一项不可或缺的业务活动。合格的数据跨境传输方案&#xff0c;应考虑以下要素&#xff1a; 法律合规性&#xff1a;确保方案符合所有相关国家的数据保护法律和国际法规&am…

对大数据的批量导入MySQL数据库

自己的库里有索引在用insert导入数据时会变慢很多 使用事务批量导入 可以配置使用springmybatis整合的方式关闭自动提交事务&#xff08;地址&#xff09;&#xff0c;选择批量导入每一百条导入使用list存储值传入到mybatis中 http://x125858805.iteye.com/blog/2369243 list.a…

c语言中的gets()函数记录

C语言中的gets()函数用于从标准输入&#xff08;通常是键盘&#xff09;中读取一行输入&#xff0c;并将其存储为C字符串。该函数会读取输入直至遇到换行符&#xff08;\n&#xff09;&#xff0c;然后丢弃换行符&#xff0c;将其余字符存储在字符串中&#xff0c;并在字符串末…

ATF是如何完成双系统切换的?

ATF&#xff08;Arm Trusted Firmware&#xff09;是一个用于ARM架构处理器的可信固件&#xff0c;它最初提供的最主要的功能就是&#xff1a;双系统切换和电源管理。 那么如何进行双系统切换呢&#xff0c;在双系统切换的示例中&#xff0c;除了CPU的跳转&#xff0c;例如CPU…

Ubuntu Updates for this repository will not be applied

1. 问题描述 在Ubuntu中使用apt update时提示如下错误信息。 Get:1 http://archive.ubuntu.com/ubuntu jammy InRelease [270 kB] Get:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB] Get:3 http://archive.ubuntu.com/ubuntu jammy-backports InRele…

使用静态方法接受对象参数

我们先来看一个例子 public class MyInteger { private int value; // 构造函数 public MyInteger(int value) { this.value value; } // 实例方法 public boolean isEven() { return value % 2 0; } // 静态方法接受int参数 public static boolean isEvenStatic…

leetcode打卡#day44 1049. 最后一块石头的重量 II、494. 目标和、474. 一和零

1049. 最后一块石头的重量 II class Solution { public:int lastStoneWeightII(vector<int>& stones) {vector<int> dp(1501, 0);int sum 0;for (int i 0; i < stones.size(); i) {sum stones[i];}int target sum / 2;for (int i 0; i < stones.si…

动态功能连接评估方法的变异性

摘要 背景&#xff1a;动态功能连接(dFC)已成为理解大脑功能的一种重要测量指标。虽然已经开发了各种各样的方法来评估dFC&#xff0c;但目前尚不清楚方法的选择会如何影响结果。在这里&#xff0c;本研究旨在考察常用dFC方法的结果变异性。 方法&#xff1a;本研究在Python中…

IT人的拖延——拖是因为不想离开“舒适区”?

人都是求“稳”的,在一个区域内呆了很久,也很舒适了,如果冒险离开进入未知的区域,万一结果不好怎么办?万一自己不适合怎么办?万一这个区域有着自己难以忍受的东西怎么办?这些对未知区域的恐惧感让我们在面对应该要做的事情时,不自觉地又拖延了起来。比如,我们在面临需…

【TF-IDF|1】深入解析TF-IDF算法—基础介绍

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种用于文本挖掘和信息检索的加权技术&#xff0c;用来评估一个词语对于一个文档集或一个语料库的重要程度。下面我们将从理论到实践&#xff0c;逐步解析TF-IDF算法&#xff0c;并编写一个简单的实…

阻塞IO、非阻塞IO、IO复用的区别 ?(非常详细)零基础入门到精通,收藏这一篇就够了

前言 在《Unix网络编程》一书中提到了五种IO模型&#xff0c;分别是&#xff1a;阻塞IO、非阻塞IO、IO复用、信号驱动IO以及异步IO。本篇文章主要介绍IO的基本概念以及阻塞IO、非阻塞IO、IO复用三种模型&#xff0c;供大家参考学习。 一、什么是IO 计算机视角理解IO: 对于计…

const与static区别

const与static的主要区别在于它们修饰的对象的行为和属性。 const 用于声明一个值不能被修改的常量。它主要用于定义常量、修饰指针、函数的输入参数和返回值&#xff0c;以确保这些值在程序运行期间保持不变。const修饰的变量或对象具有不可变性&#xff0c;这有助于提高程序的…

算法练习(一)——数学公式,逻辑思维,DFS递归

算法练习 题目一题干解法代码解释 Tips 题目二题干解法代码解释 Tips 题目三题干解法代码解释 Tips 题目一 题干 给定一个数a&#xff0c;这个a是可以由一组m个连续的正整数相加得到的&#xff0c;求这个m的最小值。举例&#xff1a;a211234561011&#xff0c;会发现10和11是…

VD1011 单节锂离子充电电池保护 2.8V过放保护 SOT-23小封装芯片

VD1011&#xff0c;内置高精度电压检测电路和延迟电路以及内置MOSFET&#xff0c;是用于单节锂离子/锂聚合物可再充电 电池的保护IC。 本IC适合干对1节锂离子/锂聚合物可再充电电池的过充电、过放电和过电流进行保护。 VD1011具备如下特点 高精度电压检测电路 过充电检测电压 …

JDK8-17新特性

一、JDK8新特性:Lambda表达式 1.Lambda表达式及其使用举例 Lambda是一个匿名函数&#xff0c;我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格&#xff0c;使Java的语言表达能力…

类加载的验证阶段你不知道的东西

一、验证是链接阶段&#xff08;验证&#xff0c;准备&#xff0c;解析&#xff09;的第一步&#xff0c;是验证.class文件中的二进制字节流被转换成的Java虚拟机里的Class对象是否合法的步骤。主要作用就是确保被加载的类的正确性、安全性及符合Java虚拟机规范&#xff0c;确保…

nc网络收发测试-tcp客户端\TCP服务器\UDP\UDP广播

netcat&#xff08;nc&#xff09;&#xff1a; 作用&#xff1a;一个功能强大的网络工具&#xff0c;提供了简单的网络测试和网络编程功能。工作原理&#xff1a;可以用于建立TCP或UDP连接&#xff0c;并发送和接收数据。示例用法&#xff1a; 监听TCP端口&#xff1a;nc -l 1…