当前位置: 首页 > java >正文

深入了解Vue2和Vue3的响应式原理

想必大家在学习vue的时候都会有这样的疑问,自己在学习JavaScript的时候,不论要修改什么内容,只有在页面刷新的时候,我们的值才会发生更新变化,但是当我们在一个vue项目中进行一样的操作的时候,就可以实现实时的变化,这是为什么呢,这是因为vue当中可以实现响应式数据更新,什么是响应式数据更新?到底是通过什么来进行实现的呢?

vue2:

vue2中是通过Object.defineProperty () 来实现响应式数据更新的,当你创建了一个vue对象,并且向它的data里面添加了部分数据,这些就变成了响应式数据,vue会遍历所有的数据,然后使用Object.defineProperty () 把这些数据添加上getter和setter,这样再我们的vue当中就会自动追踪依赖,当这些数据property被访问和修改时进行通知变更。所以就是说,我们的响应式是通过getter和setter来实现数据实时更新的,当响应式数据的值通过setter被修改时,vue 会通知所有与此数据相关的所有 Watcher。这些 Watcher 会记录下数据的变化,并准备更新视图。

watchervue2响应式系统的"中枢神经系统",扮演着重要的角色,它主要会承担三大核心职责,

1.收集依赖 2.变更检测 3.更新调度,这里我就不细讲了大家可以去了解一下

但是需要注意的是,vue不能检测到数组和对象的变化,这是因为我们是通过索引来进行更新赋值的,但是索引赋值我们的更新不会触发setter函数,因为只有当我们的数据更新触发了setter函数,才能实现响应式更新,但是当我们使用数组的一些方法的时候,比如 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 等。这些方法会触发响应式更新。

下面举一个例子:

<div id="app"><ul><!-- 使用 v-for 指令渲染数组 --><li v-for="(item, index) in list" :key="index">{{ item }}</li></ul><button @click="updateByIndex">直接索引修改</button><button @click="updateBySplice">用 splice 修改</button>
</div><script>new Vue({el: '#app',data: {list: [1, 2, 3]},methods: {// 直接使用索引赋值修改数组元素updateByIndex() {this.list[0] = 4;console.log('索引赋值后数组:', this.list);},// 使用 splice 方法修改数组元素updateBySplice() {this.list.splice(0, 1, 4);console.log('splice 修改后数组:', this.list);}}});
</script>

这里实现的效果就是,当我们调用updateByIndex()方法的时候,我们的数组的值会被修改,但是我们的vue页面不会监听到这个数据的变化,就不会更新,打印的时候是【4,2,3】,但是页面上显示的还是【1,2,3】,但是当我们去调用updateBySplice()这个方法,不管是我们的打印还是页面上的更新,都显示的【4,2,3】,这就说明了我们的splice可以调用我们的setter方法从而实现页面的响应式数据更新,这个情况就可以证明的想法

vue3:

vue3中是通过proxy来实现的响应式对象,我们在vue3中实现响应式对象的方法有两个,一个是ref,一个是reactive,这两个的区别就是ref可以用于所有的类型,但是reactive只能用于数组和对象类型,ref如果封装的是一个简单类型的对象,那么它不会使用proxy进行状态管理,它会使用gettersetter方法来进行管理,因为简单类型的响应式操作比较简单,proxy中会封存很多的方法,所以我们不在proxy中实现响应式数据更新,这样可以提高性能,而且proxy也只能用在数组或者对象这种类型,不能用于简单类型的。

function reactive(obj) {return new Proxy(obj, {get(target, key) {track(target, key)return target[key]},set(target, key, value) {target[key] = valuetrigger(target, key)}})
}function ref(value) {const refObject = {get value() {track(refObject, 'value')return value},set value(newValue) {value = newValuetrigger(refObject, 'value')}}return refObject
}

track是一个核心函数,用于在访问响应式对象属性的时候进行收集依赖

使用effect()定义一个副作用函数,该函数会读取 数据的值并打印到控制台。当 effect() 执行时,会将当前函数设置为activeEffect,并在读取数据时触发track函数,完成依赖收集。

trigger函数是用于触发依赖更新的关键部分。它的主要作用是:当数据发生变化时,通知所有依赖该数据的副作用函数(如组件的渲染函数)重新执行,从而实现视图的自动更新。

// 依赖收集和触发相关的全局变量
const targetMap = new WeakMap();// 当前活跃的副作用函数
let activeEffect = null;// 用于收集依赖的函数
function track(target, key) {if (activeEffect) {// 获取与目标对象关联的依赖映射,如果没有则创建一个新的 Maplet depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}// 获取与属性名关联的依赖集合,如果没有则创建一个新的 Setlet dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}// 将当前活跃的副作用函数添加到依赖集合中dep.add(activeEffect);}
}// 用于触发依赖更新的函数
function trigger(target, key) {// 获取与目标对象关联的依赖映射const depsMap = targetMap.get(target);if (depsMap) {// 获取与被修改属性关联的依赖集合const dep = depsMap.get(key);if (dep) {// 遍历依赖集合,执行每个副作用函数dep.forEach(effect => {effect();});}}
}// 创建响应式对象的函数
function reactive(target) {return new Proxy(target, {get(target, key, receiver) {// 触发依赖收集track(target, key);const res = Reflect.get(target, key, receiver);// 如果属性值是对象,则递归创建响应式对象if (typeof res === 'object' && res !== null) {return reactive(res);}return res;},set(target, key, value, receiver) {const oldValue = target[key];// 使用 Reflect.set 修改属性值const result = Reflect.set(target, key, value, receiver);// 如果新旧值不同,则触发依赖更新if (oldValue !== value) {trigger(target, key);}return result;}});
}// 定义副作用函数的函数
function effect(fn) {// 将传入的函数作为当前活跃的副作用函数activeEffect = fn;// 执行副作用函数,进行依赖收集fn();// 执行完成后,重置当前活跃的副作用函数activeEffect = null;
}// 示例:创建一个响应式对象并定义一个副作用函数
const state = reactive({ count: 0 });// 定义一个副作用函数,每当 state.count 变化时,会重新执行
effect(() => {console.log(`count is: ${state.count}`);
});// 修改 state.count,触发 trigger 函数,重新执行副作用函数
state.count = 1;  // 输出:count is: 1
state.count = 2;  // 输出:count is: 2

区别:

特性Vue 2Vue 3核心差异说明
实现原理Object.definePropertyProxyProxy 直接代理整个对象,无需递归初始化属性
数组响应式需重写数组方法(push/pop/shift 等)原生支持数组索引/长度修改Vue 3 可直接通过索引修改数组(如 arr[0]=1)或修改 length
动态新增属性Vue.set() / Vue.delete()直接赋值生效Vue 3 中 obj.newProperty = value 自动触发响应式
嵌套对象初始化初始化时递归遍历所有属性按需惰性代理(访问时触发)Vue 3 减少初始化开销,提升性能
性能优化初始化时递归所有数据,性能压力较大惰性代理 + 缓存机制,内存占用更低大型对象/数组场景下 Vue 3 性能优势明显
响应式 API仅通过 data 选项提供 ref() / reactive() 等独立 APIVue 3 可在组件外灵活创建响应式数据
Map/Set 集合支持不支持原生支持Vue 3 可直接响应 MapSetWeakMap 等集合类型的变化
调试能力较弱增强的调试钩子(onTrack/onTriggerVue 3 提供响应式依赖追踪和触发调试工具

http://www.xdnf.cn/news/10322.html

相关文章:

  • OneRef论文精读(补充)
  • 【设计模式-3.4】结构型——代理模式
  • 【位运算】两整数之和(medium)
  • DAY 34 超大力王爱学Python
  • 设计模式——责任链设计模式(行为型)
  • Linux线程同步实战:多线程程序的同步与调度
  • 在 SpringBoot+Tomcat 环境中 线程安全问题的根本原因以及哪些变量会存在线程安全的问题。
  • 代谢组数据分析(二十六):LC-MS/MS代谢组学和脂质组学数据的分析流程
  • 【Linux】shell的条件判断
  • gin 常见中间件配置
  • 系统思考:整体观和心智模式
  • Chrome 通过FTP,HTTP 调用 Everything 浏览和搜索本地文件系统
  • 基于STM32单片机CO气体检测
  • C56-亲自实现字符串拷贝函数
  • python连接邮箱,下载附件,并且定时更新的方案
  • SSM框架前后端网站显示不出来图片
  • stm32——SPI协议
  • 随机响应噪声-极大似然估计
  • 飞腾D2000与FPGA结合的主板
  • C语言基础(08)【循环结构】
  • 吴恩达MCP课程(2):research_server
  • 深入剖析Java类加载机制:双亲委派模型的突破与实战应用
  • 头歌java课程实验(Java面向对象 - 包装类)
  • C++语法系列之右值
  • vedio.ontimeupdate()和video.onloadeddata()
  • C++二叉树常见OJ题分析
  • 2025-05-31 Python深度学习10——模型训练流程
  • 一些常用的命令
  • 1.JS逆向简介
  • JSR 303(即 Bean Validation)是一个通过​​注解在 Java Bean 上定义和执行验证规则​​的规范