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

深入了解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 提供响应式依赖追踪和触发调试工具

相关文章:

  • 线程与进程
  • CppCon 2014 学习:C++ in Huge AAA Games
  • 美业+智能体,解锁行业转化新密码(2/6)
  • C语言进阶--程序的编译(预处理动作)+链接
  • Java实现中文姓名转拼音生成用户信息并写入文件
  • Java求职者面试指南:DevOps技术栈深度解析
  • NodeJS全栈开发面试题讲解——P7 DevOps 与部署和跨域等
  • LangChain-结合GLM+SQL+函数调用实现数据库查询(三)
  • 【配置指南】Dify部署超全配置参考手册
  • Python 训练营打卡 Day 32-官方文档的阅读
  • 英语写作中“专注于”focus on、concentrate的用法
  • 考研系列—操作系统:第四章、文件管理(part.2)
  • 哈希:闭散列的开放定址法
  • 鸿蒙OSUniApp集成WebGL:打造跨平台3D视觉盛宴#三方框架 #Uniapp
  • 【 SpringCloud | 微服务 网关 】
  • CMake指令:add_executable
  • 2024年数维杯国际大学生数学建模挑战赛A题飞行器激光测速中的频率估计问题解题全过程论文及程序
  • 79. Word Search
  • 西瓜书第十一章——降维与度量学习
  • π0论文阅读
  • 建设一个网站首先需要什么条件/违禁网站用什么浏览器
  • 怎么看一家网站是谁做的/小说搜索风云榜排名
  • 免费一键自助建站官网/陕西今日头条新闻
  • 网页制作培训多少钱一天/移动网站如何优化排名
  • 北京集团 网站建设/对百度竞价排名的看法
  • 苏州网站制作好的公司/怎么做网络营销推广