Vue.js 核心知识点
好的,我们深入探讨 Vue.js 的这些核心知识点。
1. 响应式原理:Vue 2 vs Vue 3
Vue 2: Object.defineProperty
Vue 2 通过遍历一个对象的所有属性,并使用 Object.defineProperty 将它们转换为 getter 和 setter 来实现响应式。
工作原理:
- 初始化:遍历
data函数返回对象的所有属性。 - 递归转化:对每个属性使用
Object.defineProperty设置 getter 和 setter。 - 依赖收集:在 getter 中,将正在计算的组件(Watcher)收集为这个属性的依赖。
- 触发更新:在 setter 中,当属性值变化时,通知所有收集到的依赖(Watcher)进行更新(重新渲染)。
缺点:
- 无法检测对象属性的添加或删除:因为
Object.defineProperty是在初始化时针对已有属性设置的。需要使用Vue.set或Vue.delete来解决。 - 数组监听受限:无法直接监听数组索引的设置(
arr[index] = newValue)和length的变化。Vue 2 通过重写数组的 7 个变异方法(push,pop,shift,unshift,splice,sort,reverse)来 hack 实现响应式。 - 性能瓶颈:初始化时需要递归遍历整个对象,对于大型对象,性能开销较大。
Vue 3: Proxy
Vue 3 使用 ES6 的 Proxy 来创建响应式对象。Proxy 可以拦截对象上的基本操作,功能远比 Object.defineProperty 强大。
工作原理:
- 创建代理:使用
reactive()函数包裹一个对象,返回一个 Proxy 代理。 - 拦截操作:Proxy 可以拦截多达 13 种操作,包括属性的 get、set、delete、has 等。
- 依赖收集:在
get拦截中收集依赖(副作用函数,如组件的渲染函数)。 - 触发更新:在
set或deleteProperty拦截中,触发所有收集的依赖。
优点:
- 完美检测新增和删除属性:因为
Proxy拦截的是对整个对象的操作,而不是特定属性。obj.newProperty = value和delete obj.oldProperty都能被捕获。 - 原生支持数组:
arr[index] = value和arr.length = newLength都能被set拦截器捕获,无需 hack 数组方法。 - 更好的性能:
Proxy是“懒”处理的,只有在访问到某个深层属性时才会递归将其转化为 Proxy,减少了初始化开销。- 提供了更细粒度的拦截,为后续优化提供了空间(如编译时优化)。
- 支持更多数据结构:
Proxy可以代理Map,Set,WeakMap,WeakSet等集合类型,而Object.defineProperty只能代理普通对象。
对比总结:
| 特性 | Vue 2 (Object.defineProperty) |
Vue 3 (Proxy) |
|---|---|---|
| 数据监听 | 对象已有属性 | 整个对象,动态属性(增删)也无压力 |
| 数组支持 | 需要重写方法 hack | 原生完美支持 |
| 性能 | 初始化全量递归,性能较差 | 惰性递归,性能更优 |
| 数据结构 | 仅支持 Object、Array | 支持所有 ES6+ 集合类型 |
2. Composition API vs Options API
Options API
Vue 2 的传统方式,通过一系列选项(data, methods, computed, watch, lifecycle hooks)来组织代码。
- 优点:结构清晰,强制将代码按选项分类,对初学者友好。
- 缺点:
- 逻辑关注点分离:一个功能的代码(如从API获取用户信息)可能被分散到
data,methods,mounted等多个选项中,理解和维护复杂功能时,需要在代码中不断“跳跃”。 - 逻辑复用困难:主要通过 mixins 实现,容易发生命名冲突,且来源不清晰。
- 逻辑关注点分离:一个功能的代码(如从API获取用户信息)可能被分散到
Composition API
Vue 3 引入的新范式。它允许你使用导入的函数(如 ref, reactive, onMounted)来定义组件的逻辑,像一个普通的 JavaScript 函数一样自由组织代码。
- 核心思想:将逻辑组合到组合式函数中,而不是按选项组织。
- 优点:
- 更好的逻辑组织和复用:
- 组织:可以将一个功能的所有相关代码(状态、计算属性、方法、生命周期)放在一起,代码内聚性极高。
- 复用:逻辑可以轻松提取到“组合式函数”中,在多个组件间复用,且没有 mixin 的缺点。
- 更好的 TypeScript 支持:使用普通的变量和函数,类型推断非常自然和清晰。
- 更灵活的代码组织:代码不再被选项限制,可以像写普通函数一样自由组织。
- 更好的逻辑组织和复用:
示例对比:一个跟踪鼠标位置的功能
-
Options API (分散)
// Vue 2 组件 export default { data() { return { x: 0, y: 0 } }, methods: { updatePosition(e) { this.x = e.pageX; this.y = e.pageY; } }, mounted() { window.addEventListener('mousemove', this.updatePosition); }, beforeUnmount() { // Vue 3 是 unmounted window.removeEventListener('mousemove', this.updatePosition); } } -
Composition API (内聚)
// Vue 3 组件 import { ref, onMounted, onUnmounted } from 'vue'; export default { setup() { // 鼠标逻辑 - 所有相关代码都在一起! const x = ref(0); const y = ref(0); const updatePosition = (e) => { x.value = e.pageX; y.value = e.pageY; }; onMounted(() => window.addEventListener('mousemove', updatePosition)); onUnmounted(() => window.removeEventListener('mousemove', updatePosition)); // 可以继续写其他逻辑... return { x, y }; // 模板中需要使用的数据 } };甚至可以轻松地将其提取成一个可复用的组合式函数:
// useMouse.js import { ref, onMounted, onUnmounted } from 'vue'; export function useMouse() { const x = ref(0); const y = ref(0); const updatePosition = (e) => { x.value = e.pageX; y.value = e.pageY; }; onMounted(() => window.addEventListener('mousemove', updatePosition)); onUnmounted(() => window.removeEventListener('mousemove', updatePosition)); return { x, y }; } // 在组件中使用 import { useMouse } from './useMouse'; export default { setup() { const { x, y } = useMouse(); // 逻辑清晰且可复用! return { x, y }; } };
3. 虚拟DOM与Diff算法
为什么要用VDOM?
直接操作真实DOM(如 document.createElement)的代价非常昂贵。频繁的DOM操作是网页性能瓶颈的主要原因。
VDOM是一个用来描述真实DOM的JavaScript对象。它的核心价值在于:
- 抽象:为开发者提供了一个声明式的API(如Vue的模板),无需直接操作DOM。
- 性能:通过Diff算法比较新旧VDOM的差异,然后批量、高效地将最小变化更新到真实DOM上,减少不必要的DOM操作。
Vue的Diff算法大致流程
Vue的Diff算法是同级比较、深度优先的。
- 同层比较:不会跨层级比较节点。如果发现同一层的节点类型不同(如从
div变为p),Vue会直接销毁旧节点及其子节点,创建新节点并插入。这是为了降低算法复杂度。 - Key的作用:
key是节点的唯一标识。Diff算法通过key来精确判断两个节点是否是同一个。没有key时,Vue会使用“就地复用”策略,如果节点顺序发生变化,可能会错误地复用元素而不是移动它们,导致状态错乱。使用key可以帮助Vue高效地识别和重新排序元素,避免不必要的渲染和状态问题。 - 双端比较:Vue 2 的核心 Diff 算法借鉴了 snabbdom 的双端比较策略。它会同时比较新旧子节点数组的头头、尾尾、头尾、尾头四个位置,试图找到可复用的节点,最大程度减少移动操作。
- 静态提升 & Patch Flags (Vue 3优化):Vue 3 的编译器会做更多优化。
- 静态提升:将静态节点提升到渲染函数之外,每次渲染时复用,避免重复创建VDOM。
- Patch Flags:在编译时分析动态绑定的类型(如
class,style,text),在VDOM节点上打上标记。Diff时可以直接根据标记定位到需要对比的动态内容,跳过静态内容的对比,极大提升了Diff效率。
4. 生命周期
Vue 3 的生命周期与 Vue 2 大部分相似,但为了配合 Composition API,名称有细微变化,并提供了对应的组合式API函数。
| Vue 2 Option | Vue 3 Option (Options API) | Vue 3 Composition API Hook | 触发时机 |
|---|---|---|---|
beforeCreate |
beforeCreate |
Not Needed (Use setup()) |
实例初始化后,数据观测和事件配置之前 |
created |
created |
Not Needed (Use setup()) |
实例创建完成,数据观测已完成 |
beforeMount |
beforeMount |
onBeforeMount |
挂载开始之前被调用 |
mounted |
mounted |
onMounted |
实例被挂载后调用 |
beforeUpdate |
beforeUpdate |
onBeforeUpdate |
数据更新时,虚拟DOM打补丁之前 |
updated |
updated |
onUpdated |
数据更改导致虚拟DOM重新渲染后 |
beforeDestroy |
beforeUnmount |
onBeforeUnmount |
实例销毁之前 |
destroyed |
unmounted |
onUnmounted |
实例销毁后 |
errorCaptured |
errorCaptured |
onErrorCaptured |
捕获到来自后代组件的错误时 |
setup() 函数是 Composition API 的入口,它运行在 beforeCreate 和 created 之前。 在这些钩子中编写的任何代码都应该直接在 setup() 中编写。
5. 生态库
Vue Router
Vue.js 的官方路由管理器。
- 原理:基于前端路由,监听
popstate(history模式) 或hashchange(hash模式) 事件,动态匹配预先定义的路由配置,并渲染对应的组件。 - 核心概念:
<router-view>(路由出口),<router-link>(导航), 路由配置 (routes), 编程式导航 (router.push), 导航守卫 (用于权限控制)。 - 使用场景:构建单页面应用 (SPA),实现页面切换和导航。
Pinia / Vuex
状态管理库,用于集中管理跨组件的共享状态。
-
Vuex (Vue 2/3)
- 核心概念:
- State:单一状态树,存储应用级状态。
- Getters:基于 state 的计算属性。
- Mutations:同步修改 state 的唯一方式。
- Actions:提交 mutations,可以包含异步操作。
- 流程:
Component->Dispatch(Action)->Commit(Mutation)->Mutate(State)->Render(Component)。
- 核心概念:
-
Pinia (Vue 3 推荐)
- 可以看作是 Vuex 5,是下一代状态管理工具。
- 优势:
- 更简化的API:没有
mutations,只有state,getters,actions。actions既可以处理同步也可以处理异步。 - 完美的TS支持:API设计非常类型友好。
- Composition API 风格:定义 Store 的方式很像
setup()。 - 模块化设计:默认就是多个 store,不再需要嵌套模块。
- 更简化的API:没有
-
使用场景:中大型应用,需要管理复杂的、多个组件共享的状态(如用户登录信息、购物车、全局配置等)。

浙公网安备 33010602011771号