Vue 3学习笔记2 响应式原理,proxy代理,vue2vue3差异化,虚拟dom和diff算法,v-if,v-for

发布时间:2026/10/29 2:18:28
Vue 3学习笔记2 响应式原理,proxy代理,vue2vue3差异化,虚拟dom和diff算法,v-if,v-for 摘要本文整理了 Vue 3 响应式与核心机制的常见面试要点涵盖toRef/toRaw的用法与绕过 Proxy 的几种方案、get/set的依赖收集与触发更新原理并从响应式系统、代码组织方式、TypeScript 支持、模板语法行为、全局 API 五个维度对比 Vue 2 与 Vue 3 的差异同时梳理了虚拟 DOM 与 Diff 算法、key的作用与取值建议v-if与v-show的适用场景对比以及var/let/const在作用域、重复声明、变量提升和可修改性上的区别。1. toRef 常用于为响应式对象创建一个新的响应式引用它通常用于在组件的 setup 函数中使用。toRaw 是 Vue 3 Composition API 中的一个函数用于将响应式引用reactive reference转换为普通的 JavaScript 对象一般用于性能的优化比如在某些情况下你可能需要将响应式对象转换为普通对象以避免不必要的依赖追踪。针对 reactive 绕过 Proxy 的诸多问题有以下几种解决办法i用 toRef 创建响应式链接ii直接调用原函数的变量iii使用 Object.assign() 替换原来响应式的值下面用可运行代码逐一演示toRef、toRaw以及三种绕过 Proxy 的方案。1toRef为响应式对象的某个属性创建响应式引用import{reactive,toRef}fromvueconststatereactive({count:0,name:vue})constcountReftoRef(state,count)countRef.value// 修改引用console.log(state.count)// 1源对象同步变化state.countconsole.log(countRef.value)// 2源对象变化也会同步到引用输出1、2。toRef与原对象保持双向同步且不会触发额外的依赖收集适用于把props或reactive对象的某个属性单独传给组合式函数避免解构后丢失响应性。2toRaw拿到原始对象绕过 Proxyimport{reactive,toRaw}fromvueconststatereactive({count:0})constrawtoRaw(state)console.log(rawstate)// falseraw 是原始对象raw.count100console.log(state.count)// 100修改原始对象同样生效console.log(raw.count)// 100输出false、100、100。读取/修改raw不会触发依赖收集与更新适用于把数据交给不需要响应式的第三方库如 ECharts、地图 SDK或做性能敏感的大对象处理。3方案一用 toRef 创建响应式链接import{reactive,toRef}fromvueconststatereactive({user:{name:Tom}})constnameReftoRef(state.user,name)nameRef.valueJerryconsole.log(state.user.name)// Jerry输出Jerry。用toRef把深层属性“挂”成独立引用既保留响应性又避免直接操作 Proxy 包装后的对象适用于需要把某个字段单独暴露给模板或子函数的场景。4方案二直接调用原函数的变量保存原始对象引用import{reactive,toRaw}fromvueconststatereactive({list:[1,2,3]})constrawListtoRaw(state).list// 保存原始数组引用rawList.push(4)console.log(state.list)// [1, 2, 3, 4]console.log(rawListstate.list)// falserawList 是原始数组输出[1, 2, 3, 4]、false。直接持有原始变量后操作它可避免每次读写都走 Proxy 拦截适用于批量数据处理、循环内高频读写等对性能敏感的场景但要注意此时不会触发视图更新。5方案三用 Object.assign() 替换原来响应式的值import{reactive}fromvueconststatereactive({a:1,b:2})Object.assign(state,{a:10,c:3})console.log(state)// { a: 10, b: 2, c: 3 }输出{ a: 10, b: 2, c: 3 }。Object.assign通过 Proxy 的set拦截逐项赋值新增属性也能被追踪适用于需要整体合并/重置对象字段、又不想丢失响应性的场景。2. get() 和 set() 是 Vue 3 Composition API 中用于访问和修改响应式对象属性的方法。get() 方法用于获取响应式对象的属性值只有在读取该值时才会触发依赖收集否则不会执行。set() 方法用于设置响应式对象的属性值只有在写入新值时才会触发更新否则不会执行。其中 set(newValue) 的 newValue 即为新值。3. Vue 2 和 Vue 3 的区别响应式系统Vue 2 基于Object.defineProperty劫持对象的每个属性因此无法监听对象新增/删除属性以及数组索引赋值需要借助Vue.set/Vue.delete和重写数组方法来打补丁Vue 3 改用Proxy代理整个对象能天然拦截属性的读取、设置、新增、删除以及数组索引修改不再需要这些特殊 API响应式能力更完整、行为也更符合直觉。代码组织方式Vue 2 使用 Options API把data、methods、computed、生命周期等按选项分门别类组件逻辑复杂时同一功能的代码会被拆散到不同选项里Vue 3 引入 Composition APIsetup/script setup允许按功能把相关的ref、computed、watch和函数写在一起逻辑内聚性更强也更容易抽成可复用的组合式函数。TypeScript 支持Vue 2 对 TypeScript 的支持比较有限需要借助额外库且类型推断不够精准Vue 3 完全用 TypeScript 重写类型定义更完善对 TS 用户更友好组合式函数也更容易获得类型推断。模板与语法行为Vue 2 强制组件只能有一个根节点常需要加无意义的包裹元素且v-if与v-for同时使用时v-for优先级更高导致v-if在每次循环中重复判断Vue 3 支持多根节点Fragment并把v-if优先级调整为高于v-for条件不满足时连循环都不会执行同时v-model统一了.sync的功能并支持在一个组件上绑定多个v-model。全局 API 与应用实例Vue 2 通过new Vue()创建根实例Vue.use()、Vue.component()等全局 API 会污染全局Vue 3 改为createApp()创建独立应用实例全局 API 挂载在实例上多个应用之间互不影响隔离性更好。4. 虚拟 DOM虚拟 DOM用 JS 对象描述 DOM 结构避免频繁操作真实 DOM提升渲染效率支持跨平台渲染。因此在创建虚拟 DOM 时会生成对应的 JS 对象以及用于 Diff 的 VNode 结构Diff 算法同层级对比新旧虚拟 DOM找出最小更新差异不同层级直接新建虚拟 DOM有 key 时通过 key 映射 最长递增子序列优化节点移动降低 DOM 操作用 patch 函数来对比。下面是 Diff 算法的完整流程图不同相同否是新旧 VNode 进入 patch节点类型 / key 是否相同直接替换卸载旧节点挂载新节点同层级对比子节点子节点是否为列表patch 单个子节点更新 props / 文本建立 key 映射表key - index双端对比头头、尾尾、头尾、尾头求新节点在旧列表中的索引序列最长递增子序列LIS求最小移动集合不在 LIS 中的节点执行 move 移动新增节点 mount多余节点 unmountpatch 更新完成触发真实 DOM 操作各步骤作用说明同层级对比只比较同一层级的节点不跨层级比较把 Diff 复杂度从 O(n³) 降到 O(n)不同层级直接整棵新建。key 映射用key - index的 Map 快速判断新旧节点是否可复用避免遍历查找时间复杂度为 O(1)。最长递增子序列LIS找出旧列表中已经保持相对顺序的最长一段节点这些节点无需移动其余节点才执行移动从而把 DOM 移动次数降到最少。patch 更新对可复用节点递归比较 props、文本和子节点只把真正变化的差异落到真实 DOM 上减少重排重绘。-Diff 算法同层级对比新旧虚拟 DOM找出最小更新差异不同层级直接新建虚拟 DOM有 key 时通过 key 映射 最长递增子序列优化节点移动降低 DOM 操作用 patch 函数来对比。key节点唯一标识帮助 Diff 准确匹配新旧节点复用 DOM 元素防止列表更新时组件/表单状态错乱优化对比性能时间复杂度为 O(1)禁止用随机值优先使用唯一 id不推荐索引。没有 key 的时候需要遍历所有节点5. v-if 和 v-show对比项v-ifv-showDOM行为false → 删除DOMtrue → 创建DOM始终保留DOM切换display初始渲染开销条件为false时开销小不渲染无论真假初始都会渲染DOM开销更大切换开销频繁切换开销大不断创建销毁DOM切换开销很小只改CSS属性支持v-else / v-else-if✅ 支持❌ 不支持模板template✅ 可用分组渲染❌ 不生效生命周期切换时组件会执行mounted / unmounted不会触发挂载、卸载生命周期✅用 v-if条件很少改变一次性判断比如权限控制页面模块初始大概率不显示减少首次DOM渲染需要配合v-else、template✅用 v-show需要频繁切换显示隐藏弹窗、tab切换、下拉菜单6.letconstvar的区别作用域作用域var函数作用域let块级作用域const块级作用域重复声明变量提升全局挂载挂载在windows都是var特有的是否可修改const不能修改