Vue
Vue是一个用于构建用户界面的渐进式框架。
一、Vue2 与 Vue3 感官上的区别
核心 API 风格
特性 Vue2 Vue3 核心 API 风格 Options API(选项式 API) Composition API(组合式 API)+ Options API(兼容) 代码组织方式 按功能划分(data、methods、computed 等) 按逻辑关注点组织(通过 setup 函数) 组件入口写法 export default { ... }
支持 <script setup>
语法糖(更简洁)多根节点支持 不支持(必须有唯一根节点) 支持(Fragment 片段) ts支持 较差 优秀 示例代码 javascript export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } }
vue <script setup> import { ref } from 'vue' const count = ref(0) const increment = () => { count.value++ } </script>
生命周期
Vue2 生命周期 Vue3 生命周期(Composition API) 说明 beforeCreate
无(被 setup 替代) 初始化实例前(Vue3 中 setup 执行时机与之接近) created
无(被 setup 替代) 实例创建后(Vue3 中 setup 执行时机与之接近) beforeMount
onBeforeMount
挂载前 mounted
onMounted
挂载后 beforeUpdate
onBeforeUpdate
更新前 updated
onUpdated
更新后 beforeDestroy
onBeforeUnmount
销毁前(Vue3 命名更准确) destroyed
onUnmounted
销毁后(Vue3 命名更准确) errorCaptured
onErrorCaptured
错误捕获 renderTracked
onRenderTracked
新增:渲染追踪时(开发环境) renderTriggered
onRenderTriggered
新增:渲染触发时(开发环境) activated
onActivated
新增:组件激活时(keep-alive 缓存组件激活) deactivated
onDeactivated
新增:组件停用时(keep-alive 缓存组件停用时) 使用示例
vue3vue<script setup> import { onMounted } from 'vue' onMounted(() => { console.log('组件挂载完成') }) </script>
vue2
vue<script> export default { mounted() { console.log('组件挂载完成') } } </script>
插件差异
特性 Vue2 Vue3 安装方式 Vue.use(plugin)
app.use(plugin)
(通过 createApp 创建的实例)插件定义格式 插件对象需包含 install
方法,接收Vue
构造函数插件对象 install
方法接收app
实例(更灵活)全局属性注册 Vue.prototype.$xxx = xxx
app.config.globalProperties.$xxx = xxx
全局组件注册 Vue.component('xxx', component)
app.component('xxx', component)
示例
vue3jsconst MyPlugin = { install(app) { app.config.globalProperties.$myGlobal = 'Hello Vue3!' } } createApp(App).use(MyPlugin).mount('#app')
vue2
jsconst MyPlugin = { install(Vue) { Vue.prototype.$myGlobal = 'Hello Vue2!' } } Vue.use(MyPlugin)
v-model 差异
特性 Vue2 Vue3 默认绑定属性 value
(表单元素)/modelValue
(自定义组件需配置)统一为 modelValue
默认触发事件 input
(表单元素)/input
(自定义组件需配置)统一为 update:modelValue
修饰符支持 基础修饰符(.lazy/.number/.trim) 支持自定义修饰符(如 v-model.capitalize
)多值绑定 需通过 .sync
修饰符(如:title.sync
)直接支持多 v-model
(如v-model:title
)其他指令差异
指令 Vue2 行为 Vue3 行为 v-for 与 v-if 优先级 v-for 优先级高于 v-if(不推荐同用) v-if 优先级高于 v-for(避免无效循环) v-bind 合并规则 同名属性会完全覆盖 同名属性会智能合并(如 class/style) v-on 事件修饰符 .native
修饰符用于监听原生事件移除 .native
,通过emits
声明区分组件事件和原生事件vue3 新增组件、Api
组件、Api 说明 <Suspense>
新增组件,用于处理异步组件的加载状态 <Teleport>
新增组件,用于将组件渲染到指定 DOM 节点 watchEffect
新增 Api,用于响应式地执行副作用函数
二、vue2 响应式原理
响应式核心 API:
Object.defineProperty
- Vue2 响应式系统的基石是 JavaScript 原生方法
Object.defineProperty
,其核心能力是劫持对象属性的读取和修改行为。
通过该方法可以为对象属性设置getter
和setter
,从而实现数据变化的监听与响应。
- Observer:通过递归遍历数据对象,将所有属性转换为响应式数据。
- Dep:依赖收集模块,用于管理每个响应式数据的订阅者(Watcher)。
- Watcher:响应式数据的订阅者,当数据变化时触发回调。
1.1 Dep类
以下是vue2 Dep类的伪代码,具体请查阅vue2 Dep类的代码jsclass Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } removeSub(sub) { remove(this.subs, sub) } depend() { if (Dep.target) { // Dep.target 是 Watcher 实例, 在 get 方法中会将当前实例赋值给 Dep.target Dep.target.addDep(this) } } notify() { const subs = this.subs.slice() for (let i = 0; i < subs.length; i++) { subs[i].update() } } }
1.2 Watcher类
以下是vue2 Watcher类的伪代码,具体请查阅vue2 Watcher类的代码jsclass Watcher { constructor(vm, expOrFn, cb) { this.vm = vm this.expOrFn = expOrFn this.cb = cb this.depIds = {} this.value = this.get() } get() { Dep.target = this let value = this.expOrFn.call(this.vm, this.vm) Dep.target = null return value } update() { const oldVal = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldVal) } addDep(dep) { if (!this.depIds.hasOwnProperty(dep.id)) { this.depIds[dep.id] = dep dep.addSub(this) } } removeDep(dep) { if (this.depIds.hasOwnProperty(dep.id)) { delete this.depIds[dep.id] dep.removeSub(this) } } }
1.3 Observer类
以下是vue2 Observer类的伪代码,具体请查阅vue2 Observer类的代码jsclass Observer { constructor(value) { this.value = value this.dep = new Dep() this.walk(value) this.vmCount = 0 // 给值添加 __ob__ 属性,标记为响应式对象 def(value, '__ob__', this) if (Array.isArray(value)) { this.observeArray(value) } } walk(value) { const keys = Object.keys(value) for (let i = 0; i < keys.length; i++) { defineReactive(value, keys[i], value[keys[i]]) } } }
1.4 defineReactive函数
以下是vue2 defineReactive函数的伪代码,具体请查阅vue2 defineReactive函数的代码jsfunction defineReactive(obj, key, val) { const dep = new Dep() let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } return value } return val }, set: function reactiveSetter(newVal) { if (newVal === val) { return } val = newVal childOb = observe(newVal) dep.notify() } }) return dep }
1.5 数组原型方法重写
以下是vue2 数组原型方法重写的伪代码,具体请查阅vue2 数组方法重新定义的代码jsconst arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) { ob.observeArray(inserted) } ob.dep.notify() return result }) })
- Vue2 响应式系统的基石是 JavaScript 原生方法
1.6 vue2 响应式系统的缺陷与限制
- 新增 / 删除属性无法监听
原因:Object.defineProperty 只能劫持已存在的属性
解决:使用 Vue.set(obj, key, value) 或 this.$set - 数组索引 / 长度修改无法监听
原因:数组方法重写无法覆盖所有场景
解决:使用 Vue.set(arr, index, value) 或数组变异方法 - 性能问题
初始化时需要递归遍历所有属性,大型对象可能导致性能损耗
嵌套对象过深时,递归劫持会增加内存占用
原始类型响应式限制
无法直接监听基本类型(如字符串、数字),需包裹在对象中
三、vue2 渲染流程
初始化阶段
- 构造函数入口
当我们使用new Vue()
实例化一个 Vue 实例时,会调用 Vue 构造函数。
以下是伪代码,源码位置:Vue 构造函数jsfunction Vue(options) { this._init(options) }
- 初始化
_init
方法会初始化 Vue 实例的属性和方法。
以下是伪代码,源码位置:Vue 初始化方法
jsVue.prototype._init = function(options) { const vm = this; // 合并配置 vm.$options = mergeOptions(...); initLifecycle(vm); // 初始化生命周期 initEvents(vm); // 初始化事件 initRender(vm); // 初始化渲染函数 ← 关键步骤 callHook(vm, 'beforeCreate'); initInjections(vm); // 处理inject initState(vm); // 初始化props/methods/data/computed/watch initProvide(vm); // 处理provide callHook(vm, 'created'); if (vm.$options.el) { vm.$mount(vm.$options.el); // 触发挂载 } };
- 构造函数入口
模板编译阶段
- $mount 挂载入口
获取模板字符串
以下是伪代码,源码位置:Vue 挂载方法
jsconst mount = Vue.prototype.$mount; Vue.prototype.$mount = function(el) { el = document.querySelector(el); const options = this.$options; // 优先级:render > template > el if (!options.render) { let template = options.template; if (!template && el) { template = el.outerHTML; // 获取模板字符串 } if (template) { // 编译核心 ↓ const { render } = compileToFunctions(template, {...}); options.render = render; // 保存render函数 } } return mount.call(this, el); };
- 编译核心
解析模板生成AST、优化静态节点、生成渲染函数。
以下是伪代码,源码位置:Vue 编译方法
jsfunction compileToFunctions(template, options) { const ast = parse(template, options); // 解析模板字符串 if (options.optimize !== false) { optimize(ast, options); // 优化AST } const code = generate(ast, options); // 生成渲染函数 return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }
- $mount 挂载入口
虚拟DOM生成
- mountComponent
挂载组件的方法,会调用render
函数生成虚拟 DOM 树。
以下是伪代码,源码位置:Vue 挂载组件方法
jsexport function mountComponent(vm, el) { vm.$el = el; // 核心渲染函数 const updateComponent = () => { vm._update(vm._render(), hydrating); }; // 创建渲染Watcher new Watcher(vm, updateComponent, ...); }
- _render
渲染函数,会调用render
函数生成虚拟 DOM 树。
以下是伪代码,源码位置:Vue 渲染方法
jsexport function render() { const vm = this; const { render } = vm.$options; return render.call(vm, vm.$createElement); }
- _createElement
创建虚拟 DOM 节点的方法。
以下是伪代码,源码位置:Vue 创建虚拟 DOM 方法
jsexport function _createElement(tag, data, children) { if (typeof tag === 'function') { return createComponent(tag, data, children); } else { return createElement(tag, data, children); } }
- mountComponent
组件渲染
- createComponent
组件渲染从父组件的 patch 过程开始,当遇到组件占位 VNode 时触发组件渲染。
创建组件实例的方法。
以下是伪代码,源码位置:Vue 创建组件实例方法
js// 组件init钩子 const componentVNodeHooks = { init(vnode) { const child = vnode.componentInstance = new vnode.componentOptions.Ctor(options); child.$mount(undefined); // 子组件挂载 } }; function createComponent(vnode) { if (isDef(vnode.data.hook?.init)) { vnode.data.hook.init(vnode); // 触发组件初始化 } }
- createComponent
更新阶段
- _update
更新组件的方法,会调用patch
函数对比新旧虚拟 DOM 树,更新真实 DOM。
以下是伪代码,源码位置:Vue 更新方法
jsexport function _update(vm, hydrating) { const prevVnode = vm._vnode; const vnode = vm._render(); vm._vnode = vnode; if (!prevVnode) { // 初始化渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { // 对比更新 vm.$el = vm.__patch__(prevVnode, vnode); } }
- patch
对比新旧虚拟 DOM 树,更新真实 DOM 的方法。
以下是伪代码,源码位置:Vue 对比更新方法
jsexport function __patch__(oldVnode, vnode, hydrating, removeOnly) { if (isUndef(oldVnode)) { // 初始化渲染 return createElm(vnode, insertedVnodeQueue); } else { // 对比更新 return patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly); } }
- _update
挂载阶段
- 触发mounted钩子
组件挂载完成后触发的钩子函数。
以下是伪代码,源码位置:Vue mounted 钩子
jsexport function mounted() { const { mounted } = this.$options; if (mounted) { mounted.call(this); } }
- 触发mounted钩子
四、vue2 渲染流程图
五、vue3 响应式原理
响应式核心 API:
Proxy
- 完整拦截:可拦截对象的所有操作(包括属性添加/删除)
- 惰性代理:嵌套对象在访问时才转为响应式,减少初始化开销
- 数组支持:无需重写数组方法,直接支持索引修改和长度变化
1.1 reactive 核心创建逻辑
以下是伪代码,源码位置:Vue 响应式核心方法jsfunction createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { // 1. 非对象直接返回 if (!isObject(target)) return target; // 2. 已代理过的对象直接返回缓存 const existingProxy = proxyMap.get(target); if (existingProxy) return existingProxy; // 3. 检查目标类型(普通对象/集合类型) const targetType = getTargetType(target); if (targetType === TargetType.INVALID) return target; // 4. 创建代理 const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ); // 5. 缓存并返回 proxyMap.set(target, proxy); return proxy; }
1.2 mutableHandlers的setter getter
基础处理器,用于处理普通对象的响应式。
以下是伪代码,源码位置:Vue 基础处理器
getterjsfunction createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { // 1. 触发依赖收集 track(target, key); // 2. 递归代理嵌套对象 const res = Reflect.get(target, key, receiver); if (isObject(res)) { return reactive(res); } // 3. 返回属性值 return res; } }
setter
jsfunction createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { // 1. 获取旧值 const oldValue = (target as any)[key]; // 2. 设置新值 const result = Reflect.set(target, key, value, receiver); // 3. 触发更新(排除原型链操作) if (target === toRaw(receiver)) { if (!hadKey) { // 新增属性 trigger(target, TriggerOpTypes.ADD, key, value); } else if (hasChanged(value, oldValue)) { // 修改属性 trigger(target, TriggerOpTypes.SET, key, value, oldValue); } } return result; }; }
1.3 track 依赖收集
触发依赖收集的方法,会在 getter 中调用。
以下是伪代码,源码位置:Vue 依赖收集方法jsfunction track(target: object, type: TrackOpTypes, key: unknown) { // 1. 检查是否应收集依赖 if (!shouldTrack || activeEffect === undefined) return; // 2. 获取target对应的depsMap let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } // 3. 获取key对应的dep集合 let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } // 4. 将当前effect添加到dep trackEffects(dep); }
1.4 trigger 触发更新
触发依赖更新的方法,会在 setter 中调用。
以下是伪代码,源码位置:Vue 触发更新方法jsfunction trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown ) { // 1. 获取target对应的depsMap const depsMap = targetMap.get(target); if (!depsMap) return; // 2. 收集需要触发的effects let deps: (Dep | undefined)[] = []; // 3. 不同操作类型处理 if (type === TriggerOpTypes.CLEAR) { // 集合清空 deps = [...depsMap.values()]; } else if (key === 'length' && isArray(target)) { // 数组长度变化 depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { deps.push(dep); } }); } else { // 常规属性变化 if (key !== void 0) { deps.push(depsMap.get(key)); } } // 4. 触发所有关联的effects const effects: ReactiveEffect[] = []; for (const dep of deps) { if (dep) effects.push(...dep); } triggerEffects(createDep(effects)); }
1.5 整体流程图
六、vue3 渲染流程
创建应用实例
应用实例的创建是通过createApp
方法实现的,该方法会返回一个应用实例对象,后续的配置和组件注册都需要在这个实例上进行。
以下是伪代码,源码位置:Vue 创建应用实例方法jsconst createApp = ((...args) => { const app = ensureRenderer().createApp(...args); const { mount } = app; // 重写mount方法 app.mount = (containerOrSelector) => { const container = normalizeContainer(containerOrSelector); if (!container) return; const component = app._component; // 处理模板编译 if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML; } // 清空容器内容 container.innerHTML = ''; // 调用原始mount方法 const proxy = mount(container, false); return proxy; }; return app; }) as CreateAppFunction<Element>;
模板编译
模板编译是将 Vue 组件的模板字符串转换为渲染函数的过程。
分为运行时和编译时两种方式。
运行时方式:在运行时动态编译模板,性能较低。场景:cdn 加载
编译时方式:在构建时静态编译模板,性能较高。场景:SFC 单文件组件,利用 webpack 等构建工具进行编译。
以下是伪代码,源码位置:Vue 模板编译方法jsfunction baseCompile( template: string, options: CompilerOptions ): CodegenResult { // 1. 解析模板生成AST const ast = parse(template, options) // 2. AST转换(优化和转换) transform(ast, { ...options, nodeTransforms: [ ...(options.nodeTransforms || []), transformText, // 文本节点转换 transformElement, // 元素节点转换 transformIf, // v-if转换 transformFor // v-for转换 ] }) // 3. 生成渲染函数代码 return generate(ast, { ...options, mode: 'function' // 生成函数模式 }) }
挂载组件 mountComponent 挂载组件是将组件实例挂载到 DOM 元素上的过程。
以下是伪代码,源码位置:Vue 挂载组件方法jsfunction mountComponent( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) => { // 1. 创建组件实例 const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )) // 2. 设置组件实例 setupComponent(instance) // 3. 设置渲染副作用 setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) // 4. 返回组件实例 return instance; }
3.1 组件初始化 setupComponent
负责组件状态初始化,包括执行 setup 函数
以下是伪代码,源码位置:Vue 组件初始化方法jsfunction setupComponent(instance: ComponentInternalInstance) { const { props, children } = instance.vnode // 初始化props initProps(instance, props, instance.vnode) // 初始化插槽 initSlots(instance, children) // 设置组件状态:如果有setup则执行 const setupResult = setupStatefulComponent(instance) return setupResult }
3.2 组件副作用 setupRenderEffect
负责组件渲染副作用的设置,包括渲染函数的执行和更新。
以下是伪代码,源码位置:Vue 组件渲染副作用方法jsconst setupRenderEffect: SetupRenderEffectFn = (...) => { // 创建更新函数 const componentUpdateFn = () => { if (!instance.isMounted) { // 首次渲染 const subTree = (instance.subTree = renderComponentRoot(instance)) // 执行patch算法 patch(null, subTree, container, anchor, instance, parentSuspense, isSVG) // 保存真实DOM引用 initialVNode.el = subTree.el // 标记已挂载 instance.isMounted = true // 触发mounted钩子 queuePostRenderEffect(() => { instance.m && instance.m() }, parentSuspense) } else { // 更新渲染... } } // 创建响应式effect const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => queueJob(instance.update), instance.scope )) // 初始化更新任务 const update: SchedulerJob = (instance.update = () => effect.run()) update.id = instance.uid update() }
3.3 Vnode Patch
负责组件 VNode 树的对比和更新,将差异应用到真实 DOM 上。
以下是伪代码,源码位置:Vue VNode 对比和更新方法jsconst patch: PatchFn = ( n1, // 旧VNode n2, // 新VNode container, // 容器 anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false ) => { // 1. 相同节点直接更新 if (n1 === n2) return // 2. 不同类型节点直接替换 if (n1 && !isSameVNodeType(n1, n2)) { unmount(n1, parentComponent, parentSuspense, true) n1 = null } const { type, ref, shapeFlag } = n2 // 3. 根据节点类型处理 switch (type) { case Text: // 文本节点处理 processText(n1, n2, container, anchor) break case Comment: // 注释节点处理 break case Static: // 静态节点处理 break case Fragment: // Fragment处理 break default: if (shapeFlag & ShapeFlags.ELEMENT) { // DOM元素处理 processElement(...) } else if (shapeFlag & ShapeFlags.COMPONENT) { // 组件处理 processComponent(...) } else if (shapeFlag & ShapeFlags.TELEPORT) { // Teleport处理 } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // Suspense处理 } } }
3.4 调度更新
更新会被调度到微任务队列,实现批量更新。
以下是伪代码,源码位置:Vue 调度更新方法js// 任务队列 const queue: SchedulerJob[] = [] // 刷新任务队列 function flushJobs(seen?: CountMap) { // 1. 排序保证父子组件顺序 queue.sort((a, b) => getId(a) - getId(b)) // 2. 循环执行任务 for (let i = 0; i < queue.length; i++) { const job = queue[i] callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } // 3. 清空队列 queue.length = 0 } // 添加任务到队列 export function queueJob(job: SchedulerJob) { // 去重处理 if (!queue.includes(job)) { queue.push(job) // 异步执行刷新 queueFlush() } } // 异步刷新队列 function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true // 使用微任务或Promise.then currentFlushPromise = resolvedPromise.then(flushJobs) } }
vue3 整体流程图
七、模板编译差异
特性 | Vue 2 | Vue 3 |
---|---|---|
编译目标 | 生成渲染函数 | 生成优化后的渲染函数 + 元数据 |
优化策略 | 有限的静态提升 | 多级静态提升 + PatchFlags |
关键优化技术 | 静态节点标记 | 树结构打平 + 事件缓存 |
编译输出 | 纯渲染函数 | 渲染函数 + 静态节点引用 |
编译时机 | 构建时/运行时 | 构建时优先,运行时支持 |
- template编译地址:Vue 3 模板编译
- SFC 编译地址:Vue 3 SFC 编译
八、diff差异
Vue2 与 Vue3 Diff 算法特性对比
特性 | Vue 2 | Vue 3 |
---|---|---|
算法类型 | 双端比较(oldStart/oldEnd/newStart/newEnd) | 双端比较 + 最长递增子序列 |
比较策略 | 同层递归全量比较 | 基于 PatchFlags 的靶向更新 |
静态节点处理 | 全量比较 | 完全跳过 |
Key 的作用 | 建议但不强制 | 强制要求,关键优化点 |
碎片支持 | 不支持(需根节点) | 原生支持 Fragment |
性能复杂度 | O(n) | O(1) 静态节点,O(n) 动态内容 |