Appearance
Vue 核心原理
MVVM · 响应式(Observer / Proxy)· diff · 生命周期 · 虚拟 DOM · 初始化流程
一、Vue2 响应式原理(Observer / Dep / Watcher)
- MVVM 架构:Model(数据)+ View(视图)+ ViewModel(核心,负责数据与视图关联)
- 核心组成:Observer(监听器)+ Compiler(解析器)+ Watcher(订阅者)+ Dep(发布者)
Observer — 使数据可观测:
js
function defineReactive(obj, key, val) {
if (typeof val === 'object') new Observer(val) // 递归子属性
const dep = new Dep()
Object.defineProperty(obj, key, {
get() { dep.depend(); return val }, // 收集依赖
set(newVal) { if (val !== newVal) { val = newVal; dep.notify() } } // 触发更新
})
}Dep — 依赖管理器:
js
class Dep {
constructor() { this.subs = [] }
depend() { if (window.target) this.addSub(window.target) }
notify() { this.subs.forEach(sub => sub.update()) }
}Watcher — 订阅者:
js
class Watcher {
get() {
window.target = this // 将自身设为全局目标
let value = this.getter.call(this.vm, this.vm) // 触发 getter → dep 收集 watcher
window.target = undefined // 释放
return value
}
}完整流程:
- Data 通过 Observer 转换为 getter/setter
- 读取数据时触发 getter →
dep.depend()收集当前 Watcher - 修改数据时触发 setter →
dep.notify()通知所有 Watcher - Watcher 收到通知后调用
update()→ 更新视图
Object.defineProperty 的不足:只能侦测属性的读取和设置,无法侦测属性新增和删除。Vue 解决方案:Vue.set / Vue.delete API。
二、Vue3 响应式升级(Proxy)
- Vue2:
Object.defineProperty劫持属性,需深度遍历,无法监听动态新增/删除、数组索引和 length - Vue3:
Proxy拦截对象任意属性的读写、新增、删除操作;配合Reflect操作源对象,保证行为与原生一致
Vue3 响应式核心简易实现:
js
var activeEffect = null
function effect(fn) { activeEffect = fn; activeEffect(); activeEffect = null }
var depsMap = new WeakMap()
function gather(target, key) {
if (!activeEffect) return
let depMap = depsMap.get(target) || depsMap.set(target, new Map()).get(target)
let dep = depMap.get(key) || depMap.set(key, new Set()).get(key)
dep.add(activeEffect)
}
function trigger(target, key) {
const dep = depsMap.get(target)?.get(key)
if (dep) dep.forEach(e => e())
}
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) { gather(receiver, key); return Reflect.get(target, key, receiver) },
set(target, key, value, receiver) { Reflect.set(target, key, value, receiver); trigger(receiver, key); return true }
})
}
// ref 本质是 reactive({ value: xxx })三、Vue3 性能提升
编译阶段优化:
- 静态提升(hoistStatic):静态节点在 render 外创建一次,后续复用
- Patch Flag:动态标签末尾加标记,只追踪带 patchFlag 的节点,跳过逐层遍历
- 缓存事件处理函数(cacheHandler):避免每次重新生成新 function
- Fragments:template 不需要唯一根节点,减少标签层级和内存占用
源码体积:Tree-shaking 友好,按需打包(ref/reactive/computed 等仅在使用时引入)
四、Vue 数组处理
- Vue2 重写了数组的 7 个变更方法:
push、pop、shift、unshift、splice、sort、reverse - 无法通过索引直接修改数组项,需使用
Vue.set/this.$set - Vue3 使用 Proxy 可直接监听数组索引和 length 变化
五、Vue key 与 diff 算法
- diff 算法策略:深度优先,同层比较,不跨层级
- patchVnode 过程:比较文本/子节点差异,调用
updateChildren更新子节点 - updateChildren:设置新旧 VNode 头尾指针,从两端向中间比较,根据 key 映射复用节点
- key 的作用:唯一标识,帮助 diff 算法精准识别节点,实现本地复用策略,提升性能
六、Vue 生命周期
Vue2 生命周期钩子:
beforeCreate:数据观测和事件初始化之前,不能访问 data/computed/methodscreated:实例创建完成,data/computed/watch/methods 已配置,$el 未挂载beforeMount:挂载前,模板已编译但未渲染到 DOMmounted:el 被 $el 替换,模板渲染到页面,可发起 ajax 请求beforeUpdate:响应式数据更新时,DOM 还未重新渲染updated:DOM 已根据数据变化更新beforeDestroy:实例销毁前,实例仍完全可用destroyed:销毁后,所有事件监听器移除
Vue3 变化:beforeDestroy → beforeUnmount,destroyed → unmounted
Composition API 形式:onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
父子组件执行顺序:父 beforeCreate → created → beforeMount → 子 beforeCreate → created → beforeMount → mounted → 父 mounted
七、Vue 路由
- 路由本质:URL 与组件的映射关系,通过 hash 或 history 模式实现页面切换
- hash 模式:URL 中带
#,利用hashchange事件监听,兼容性好 - history 模式:利用 HTML5
pushState/replaceState,URL 更美观,需服务端配置
八、Vue 组件通信方式
- props / $emit:父子组件通信
- ref:父组件获取子组件实例
- EventBus($bus):兄弟组件通信
- $parent / $root:访问父/根组件实例
- $attrs / $listeners:祖先与后代组件传递(Vue3 中 $listeners 已移除,合并到 $attrs)
- Provide / Inject:祖先组件向后代组件注入数据
- Vuex / Pinia:全局状态管理(详见 状态管理与中间件)
九、v-show 与 v-if 区别
| 特性 | v-show | v-if |
|---|---|---|
| 控制方式 | CSS display:none,DOM 还在 | 真正添加/移除 DOM 元素 |
| 性能 | 更高初始渲染消耗 | 更高切换消耗 |
| 使用场景 | 频繁切换 | 条件很少变化 |
十、$nextTick 原理
- 作用:在下次 DOM 更新循环结束后执行回调,获取更新后的 DOM
- 核心流程:响应式数据更新 → watcher 加入队列 →
nextTick将 flushSchedulerQueue 放入 callbacks → 通过timerFunc放入微任务队列(首选 Promise → MutationObserver → setImmediate → setTimeout)
十一、keep-alive
- 作用:缓存组件实例,避免重复渲染
- 属性:
include(匹配才缓存)、exclude(匹配不缓存)、max(最多缓存数) - 生命周期:多出
activated(进入)和deactivated(离开) - 首次进入:beforeCreate → created → mounted → activated
- 再次进入:只触发 activated(跳过 created/mounted)
十二、data 为什么是函数
- 组件可能有多个实例,若 data 是对象则所有实例共享同一引用(数据污染)
- 函数返回新对象,每个实例拥有独立的数据副本
十三、虚拟 DOM(VNode)
- 概念:用 JS 对象描述真实 DOM 结构,追踪如何改变真实 DOM
- VNode 核心属性:tag、data、children、text、elm(对应真实 DOM)、key
十四、Vue2 初始化与挂载流程
new Vue → _init() → 合并配置 → initLifecycle → initEvents → initRender → beforeCreate → initState(data/props/computed/watch)→ created → 若有 el 则 $mount
mountComponent 核心:
js
new Watcher(vm, () => { vm._update(vm._render()) }, ...)
// 渲染 Watcher:初始化执行一次 + 数据变化时执行十五、Vue3 初始化流程
createApp() → mount() → render() → patch() → processComponent() → mountComponent()
十六、Vue2 vs Vue3 核心区别
- 响应式:
Object.defineProperty→Proxy - TypeScript 支持更完善
- 新增 Composition API,更好的逻辑复用
- v-if 和 v-for 优先级变化(Vue3 中 v-if 优先)
- 静态元素提升、虚拟节点静态标记
- 生命周期钩子重命名(beforeDestroy→beforeUnmount 等)
- 打包体积优化、SSR 渲染性能提升
- 支持多个根节点
十七、Vue3 新组件
Fragment:组件可以没有根标签,内部包含在 Fragment 虚拟元素中,减少标签层级
Teleport:将组件的 HTML 结构移动到指定位置(如弹窗挂载到 body)
html
<teleport to="body">
<div v-if="isShow" class="dialog">弹窗内容</div>
</teleport>Suspense:等待异步组件时渲染额外内容(loading),提供 #default 和 #fallback 两个插槽
十八、Vue3 大型表格性能优化
场景:20行 × 180列 el-table,switch 切换耗时 7-8s(Vue3 响应式开销)
优化手段:
shallowRef 替代 ref(减少 17-20%)
ref()深层递归代理所有属性,shallowRef()仅代理.value本身
jsconst data = shallowRef([]) // 仅 data.value = xxx 触发更新渲染函数中避免响应式依赖(减少 7-20%)
js// 提取为普通数组,避免重复代理 const plainCols = columns.value.map(item => ({ realWidth: item.realWidth }))v-if 替代 disabled(减少 80%)
- el-tooltip 等复杂组件会成倍增加 patch 比对耗时
html<!-- 仅需要时才创建 tooltip 组件 --> <span v-if="!showTooltip">{{ text }}</span> <el-tooltip v-else>...</el-tooltip>