Vue原理
组件化和MVVM
数据驱动视图
- 传统组件,只是静态渲染,更新还要依赖操作DOM
- 数据驱动视图 - vue MVVM
- 数据驱动视图 - react setState() useState();
MVVM

- Model(模型)
- Model 代表应用的数据和业务逻辑。它负责存储和管理应用的状态。Model 可以包含从服务器获取的数据,或应用程序中需要操作的数据
- Model 不关心 UI,它只处理数据和与数据相关的操作
- View(视图)
- View 是应用的 UI 部分,负责显示数据(即 UI 展现)。它通常是由 HTML、CSS 等组成的界面。View 关注的是如何呈现数据。
- View 被动地显示数据变化,但它不能直接修改数据
- ViewModel(视图模型)
- ViewModel 是 View 和 Model 之间的桥梁,它负责将 Model 中的数据处理为 View 所需的格式,并且处理用户交互(如点击、输入等)。ViewModel 监听用户对 View 的操作,并更新 Model 数据,然后再将更新后的数据展示给 View
- ViewModel 不直接操作 DOM,而是通过数据绑定(如 Vue 中的双向数据绑定)来驱动 UI 的更新
响应式原理
核心API
- Vue 2 主要采用 Object.defineProperty() 实现数据的劫持和依赖收集。
- Vue 3 使用 Proxy 替代了 Object.defineProperty()
响应式的核心机制
Vue2.0通过数据劫持(数据监听)+ 发布 - 订阅模式来实现响应式
- Observer(数据监听): 使用Object.defineProperty()劫持对象的属性,并在属性被读取或修改时触发回调
- Dep(依赖收集):用于管理依赖(订阅者),当数据变更,通知所有依赖更新
- Watcher(观察者): 作为Vue组件与数据的桥梁,当庭数据变化后,触发视图更新
响应式流程
- 数据初始化
- 依赖收集
- 视图更新
Object.definedProperty 缺点
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增刷新/删除属性(Vue.set Vue.delete)
- Vue.set(obj, "name", "value");
- Vue.delete(obj, key)
- 无法监听原生数组,通过重写数组方法(如 push、pop、splice)来监听数组变更,但不能监听数组索引的直接修改
vDom和diff
- vdom是实现vue和react的重要基石
- diff算法是vdom中最核心、最关键的部分
虚拟DOM(Virtual DOM)
Virtual DOM(虚拟 DOM) 是 Vue、React 等前端框架中用于优化 DOM 操作的一种技术。它的核心思想是用 JavaScript 对象模拟真实 DOM 结构,然后在数据变化时对比新旧 VDOM 生成最小的更新补丁,再应用到真实 DOM,从而提高渲染性能。
为什么要有vdom
- 频繁操作DOM开销大,每次操作都需要触发回流和重绘
- 数据驱动UI复杂: 手动管理DOM变化需要写大量的代码,容易出错,难为维护
优势
- 减少直接DOM操作,在js中先计算变更,再一次性更新DOM
- 优化渲染性能:使用diff算法计算最小的dom更新,提高页面性能
- 跨平台能力,vdom不依赖浏览器,react native等移动端框架也基于vdom构建
VDOM的实现
Vue2参考的是Snabbdom作为Virtual DOM库,它的实现主要包括:
- VNode(虚拟节点):用javascript对象表示DOM结构
相当于const vnode = { tag: "div", data: { id: "app", class: "container" }, children: [ { tag: "p", data: {}, text: "Hello Virtual DOM" } ] };<div id="app" class="container"> <p>Hello Virtual DOM</p> </div> - Diff算法: 新旧VNode进行对比,计算最小修改
- 同级比较:只比较同一层的节点,不进行夸层对比,减少计算量
- Key机制: 通过key标识节点,提高Diff速度
- 双端对比(双指针优化):减少无用遍历,优化列表更新
- 四种节点变更情况:
- 新增节点 -> 直接插入
- 删除节点 -> 直接移除
- 修改节点 -> 更新属性、文本
- 移动节点 -> 调整顺序,减少dom操作
- Patch过程:把差异更新到真实DOM
通过snabbdom学习VDOM
- h函数
- vdom数据结构
- patch函数
VDOM总结
- 用js模拟DOM结构
- 新旧vnode对比,得出最小的更新范围,最后更新DOM
- 数据驱动视图的模式下,有效控制DOM操作
diff算法
概览
Diff(差异对比算法)用于比较新旧 Virtual DOM(VNode),找出最小变化,并将变更高效地应用到真实 DOM。
- 新旧VNode是否是同一个节点
- 对比属性和文本内容
- 对比子节点(双端对比 + key机制优化)
背景
- 树diff的时间 复杂度O(n^3)
- 遍历tree1
- 遍历tree2
- 排序
- 优化时间复杂度到O(n)
- 只比较统一层级,不跨级比较
- tag不相同,则直接删除重建,不再深度比较
- tag和key,两者都相同,则认为是相同节点,不再深度比较
diff算法详细过程
- 判断是否是同一节点
- 相同:直接对比属性&子节点
- 不相同:直接删除旧节点,创建新节点
- 判断规则:
- 标签相同
- key相同
- 都是文本节点
const oldVNode = { tag: "p", text: "Hello" }; const newVNode = { tag: "p", text: "Hello Vue" }; // 只修改文本,不删除 p 标签 patch(oldVNode, newVNode);
- 对比属性和文本
- 属性不同 - 更新属性
- 文本不同 - 更新textContent
最终更新:// 旧 VNode const oldVNode = { tag: "p", data: { class: "text" }, text: "Hello" }; // 新 VNode const newVNode = { tag: "p", data: { class: "text updated" }, text: "Hello Vue" }; // Vue Patch 过程: // 1. class 变更 → 仅修改 class // 2. 文本变更 → 仅修改 textContent patch(oldVNode, newVNode);<p class="text">Hello</p> ↓ <p class="text updated">Hello Vue</p> - 对比子节点
如果节点相同,vue递归比较chidren:- 旧节点有,新节点没有 -> 删除旧节点
- 新节点有,旧节点没有 -> 直接新增
- 新旧都有 -> 进行双端对比+key机制优化
- 双端diff优化
- 从头开始比较(左侧)
- 从尾部开始比较(右侧)
- 移动节点:使用key识别相同节点,避免删除+重建
vdom和diff算法总结
- patchVNode
- addVNodes removeVNodes
- updateChildren(key的重要性)
- 细节不重要,updateChildren的过程不重要,不要深究
- vdom核心概念很重要:h、vnode、patch、diff、key
- vdom存在的价值更加重要:数据驱动视图,控制DOM
LIS算法
在 Vue 2 的 Diff 过程中,列表比较采用双端比较(双指针优化),但如果有大量元素位置变化,Vue 2 可能会执行多次插入和删除,导致不必要的 DOM 操作。
Vue 3 进一步优化了 Diff 算法,引入了 LIS(最长递增子序列,Longest Increasing Subsequence),减少 DOM 移动次数,使列表更新更高效。
LIS 是指在一个数组中,找到最长的递增子序列(不要求连续), Vue 3 的 Diff 算法中,LIS 用来找到可以复用的元素,避免不必要的移动。
输入: [10, 9, 2, 5, 3, 7, 101, 18]
最长递增子序列: [2, 3, 7, 18](长度 4)
vue3Diff过程
- 从前往后找到相同前缀(头部相同不变)
- 从后往前找到相同后缀(尾部相同不变)
- 对比中间不同部分
- 查找最长递增子序列
- 只移动非LIS部分
- LIS部分直接复用,避免多余操作
vue2和vu3 diff更新对比
<!-- 旧列表 -->
<ul>
<li key="a">A</li>
<li key="b">B</li>
<li key="c">C</li>
<li key="d">D</li>
</ul>
<!-- 新列表(调整顺序) -->
<ul>
<li key="a">A</li>
<li key="c">C</li>
<li key="d">D</li>
<li key="b">B</li>
</ul>
- vue2处理方式:
- 发现 A 没变
- 发现 B 位置变了(误认为B被删除,再新增到最后)
- C 和 D 位置不变
- 最终:删除B,创建B,导致额外的dom操作
- vue3处理方式
- A 位置不变
- C->D 不一定能够
- B(需要调整)
- 最终:只移动B,而不是先删除+新增
模版编译
js的with语法
with 语句用于扩展作用域链,让代码在一个对象的作用域内执行,避免重复书写对象名。
const person = {
name: "Alice",
age: 25,
job: "Engineer"
};
with (person) {
console.log(name); // 直接访问 person.name
console.log(age); // 直接访问 person.age
console.log(job); // 直接访问 person.job
}
with 会影响作用域链的解析,导致代码难以阅读和调试,因此被认为是不安全的,在严格模式(strict mode)下被禁止。
vue template complier将模版编译为render函数
具体代码见vue原理 - 模版编译 - vue-template-compiler-demo
背景
- 模版不是html,有指令、插值、js表达式、能实现判断、循环
- html是标签语言,只有js才能实现判断、虚幻(图灵完备的)
- 因此,模版一定是转换为某种js代码,即编译模版
过程
- 解析,将vue模版解析为AST(抽象语法树),即一个树型结构数据表示
- 转换,在ast的基础上进行转换处理
- 指令解析(v-if, v-for)
- 表达式解析({{message}})
- 静态优化(标记静态节点,提高更新性能)
- 代码生成,将AST转换为可执行的函数(render function),即h函数(vNode)
- 最终 render() 函数在组件渲染时执行,生成 Virtual DOM(虚拟 DOM),再通过 Diff 算法 更新真实 DOM。
总结
- 模版编译为render函数,执行render函数会返回vnode
- 基于vnode再执行patch和diff
- 使用webpack vue-loader 会在开发环境下编译模版(重要),属于优化手段
执行render函数生成vnode
vue组件中使用render代替了template
react一直都用render
组件更新渲染过程
组件是异步渲染的,汇总数据data修改,一次性更新
[图片上传失败...(image-441047-1741339501073)]
- 初次渲染过程
- 创建vue实例
- 解析template为render函数
- 触发响应式,监听data属性的getter 和 setter
- 执行render函数,生成vNode
- patch(elem, newVNode),将vNode转换为真实的DOM并挂载到页面上:
- 更新过程
- 修改data,触发setter(此前在getter中已被监听)
- 重新生成render函数,生成新的vNode
- patch(oldVNode, newVNode),通过diff,找到变更的节点,渲染到页面上
前端路由
前端路由的原理
前端路由的本质是在不刷新页面的情况下,更新URL并改变页面内容,他主要依赖History API 或 Hash模式来监听URL变化,并根据不同的url渲染组件
原因
- 传统的后端路由方式下,每次访问新的页面都会向服务器请求新的html,每次页面跳转都会重新加载,影响用户体验
- 前端路由让页面切换更快:
- 避免整页刷新
- 只更新部分视图
- 更适合单页面应用
前端路由的两种模式
hash路由
- hash变化会触发网页跳转,即浏览器的前进,后退
- hash变化不会刷新页面,spa必需的特点
- hash永远不会提交到server端
history路由
- 用url规范的路由,但跳转是不刷新页面
- history.pushState
- window.onpopstate
- 需要服务端支持
如何选择
- to b系统推荐用hash,简单易用,对url规范不敏感
- to c系统,可以考虑h5 history,但需要服务端支持
- 能选择简单的,就别用复杂的,要考虑成本和收益