前端八股之Vue
1065
Vue基础
1. Vue的基本原理
当一个Vue实例创建时,Vue会遍历属性,用proxy将它们转为getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
2. 双向数据绑定的原理
Vue3 的双向绑定原理基于Proxy和Reflect实现,相较于Vue2的Object.defineProperty
,Proxy提供了更强大的功能和更好的性能。以下是Vue3双向绑定的基本原理和步骤:
- 数据劫持(Reactive)
Vue3 使用Proxy
对数据对象进行劫持,监听对象的读取和修改操作。当数据发生变化时,Proxy会自动触发相应的回调函数,通知视图更新。
• Proxy:Proxy
可以拦截对象的读取(get
)和修改(set
)操作,无需像Object.defineProperty
那样递归遍历对象的每个属性。 • Reflect:Reflect
用于执行默认行为,如获取或设置属性值,确保Proxy的拦截操作不会影响正常逻辑。 - 依赖收集(Track)
当组件渲染时,Vue3会通过Proxy
的get
操作收集当前属性的依赖(即哪些组件或视图依赖于该属性)。这些依赖会被存储在一个全局的依赖映射表(targetMap
)中,以便在数据变化时快速找到需要更新的视图。
• 依赖映射表:targetMap
是一个WeakMap
,键为被劫持的对象,值为另一个Map
,存储该对象的所有属性和对应的依赖集合。 - 触发更新(Trigger)
当数据被修改时,Proxy
的set
操作会触发更新机制。Vue3会从targetMap
中找到该属性的所有依赖,并通知它们执行更新操作。
• 批量更新:为了提高性能,Vue3会将多个更新操作合并到一个微任务队列中,避免频繁的视图更新。 - 模板编译与视图更新
Vue3 的模板编译器会将模板中的指令(如v-model
)解析为对应的渲染函数。当数据变化时,渲染函数会重新执行,生成新的虚拟DOM,并通过Diff算法高效地更新真实DOM。
3. computed
和watch
的区别
对于computed
- 它支持缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当
computed
中有异步操作时,无法监听数据的变化 computed
的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的- 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用
computed
对于watch
- 它不支持缓存,数据变化时,它就会触发相应的操作
- 支持异步监听
- 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch
总结
当需要进行数值计算,并且依赖于其它数据时,应该使用computed
,因为可以利用computed
的缓存特性,避免每次获取值时都要重新计算。
当需要在数据变化时执行异步或开销较大的操作时,应该使用watch
,使用watch
选项允许执行异步操作 (访问一个API),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
4. slot是什么?有什么作用?
slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot又分三类,默认插槽,具名插槽和作用域插槽。
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
5. 常见的事件修饰符及其作用
.stop
:等同于JavaScript中的event.stopPropagation()
,防止事件冒泡.prevent
:等同于JavaScript中的event.preventDefault()
,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播).capture
:与事件冒泡的方向相反,事件捕获由外到内;- .self:只会触发自己范围内的事件,不包含子元素;
- .once:只会触发一次。
6. v-if
和v-show
的区别
- 手段:
v-if
是动态的向DOM树内添加或者删除DOM元素;v-show
是通过设置DOM元素的display样式属性控制显隐; - 编译过程:
v-if
切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show
只是简单的基于css切换; - 编译条件:
v-if
是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;v-show
是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留; - 性能消耗:
v-if
有更高的切换消耗;v-show
有更高的初始渲染消耗; - 使用场景:
v-if
适合条件不大可能改变;v-show
适合频繁切换。
7. v-model
是如何实现的,语法糖实际是什么?
作用在表单元素上
动态绑定了input的value指向了messgae变量,并且在触发input事件的时候去动态把message设置为目标值
<input v-model="sth">
// 等同于
<input :value="message" @input="message=$event.target.value">
作用在组件上
在自定义组件中,v-model
默认会利用名为value的prop和名为input的事件
本质是一个父子组件通信的语法糖,通过prop和emit实现。因此父组件v-model语法糖本质上可以修改为
<child :value="message" @input="e => message = e"></child>
8. 对keep-alive
的理解,它是如何实现的,具体缓存的是什么?
keep-alive
是Vue中一个非常重要的内置组件,用于实现组件的缓存功能。它的主要作用是保留组件的状态或避免重复渲染,从而提升性能。
1. keep-alive
的作用
keep-alive
是一个抽象组件,它不会在DOM中生成任何额外的节点,而是通过控制其包裹的动态组件(如<component>
或路由组件)来实现缓存功能。具体来说:
- 保留组件状态:当组件被切换时,
keep-alive
会将其从DOM中移除,但不会销毁该组件实例,而是将其缓存起来。当下次重新激活该组件时,直接复用缓存的实例,而不是重新创建。 - 避免重复渲染:由于组件实例被缓存,因此可以避免不必要的生命周期钩子(如
created
和mounted
)被触发,减少性能开销。
2. keep-alive
的实现原理
keep-alive
的核心实现依赖于Vu 的组件生命周期和内部机制,主要包括以下几个方面:
(1)组件缓存机制
- 当一个组件被
keep-alive
包裹时,Vue会在组件切换时检查该组件是否已经被缓存。 - 如果组件已经被缓存,则直接复用缓存中的组件实例,而不会触发
created
和mounted
等生命周期钩子。 - 如果组件未被缓存,则会正常创建并初始化组件实例,并将其存储到缓存中。
(2)缓存管理
keep-alive
内部维护了一个缓存对象(通常是一个Map
或类似结构),用于存储缓存的组件实例。- 每个组件实例通过其唯一的
key
(默认为组件的name
属性,或者通过include
/exclude
指定的规则)进行标识。 - 当组件被切换时,
keep-alive
会根据当前组件的`key 判断是否需要缓存或复用。
(3)生命周期钩子的特殊处理
- 被
keep-alive
缓存的组件会触发两个特殊的生命周期钩子:activated
:当组件被激活(从缓存中恢复)时触发。deactivated
:当组件被停用(被缓存但未显示)时触发。
- 这些钩子允许开发者在组件缓存前后执行特定逻辑。
(4)缓存限制
keep-alive
提供了max
属性,用于限制缓存组件的数量。当缓存数量超过max
时,最早缓存的组件会被移除(遵循 LRU 算法)。- 可以通过
include
和exclude
属性指定哪些组件需要被缓存或排除。
3. keep-alive
具体缓存的是什么?
keep-alive
缓存的核心内容是组件实例(VNode
和相关状态)。具体包括以下内容:
(1)组件实例
- 组件实例的所有数据、方法、计算属性、监听器等都会被完整保存。
- 例如,表单中的输入值、滚动位置、定时器等状态都会被保留。
(2)DOM 节点
- 被缓存的组件对应的 DOM 节点也会被保留在内存中,而不是从 DOM 树中移除。
- 这意味着组件的样式、布局等都不会丢失。
(3)生命周期状态
- 组件的生命周期状态会被暂停(即不会触发
unmounted
钩子),直到组件再次被激活。
4. 使用场景
keep-alive
适用于以下场景:
- 频繁切换的组件:例如标签页(Tabs)、弹窗(Modal)等。
- 需要保留状态的组件:例如表单页面、列表页面(分页、滚动位置等)。
- 性能优化:避免重复渲染复杂的组件。
5. 注意事项
- 内存占用:由于
keep-alive
会将组件实例保留在内存中,因此如果缓存的组件过多,可能会导致内存占用过高。 - 清理工作:在
deactivated
钩子中,可能需要手动清理一些资源(如定时器、事件监听器等),以避免潜在的内存泄漏。 - 动态组件:
keep-alive
通常与动态组件(<component>
)或路由组件配合使用。
9. nextTick
的原理和作用
1. nextTick
的作用
nextTick
是Vue提供的一个工具方法,用于在DOM更新完成后执行回调函数。它的主要作用是:
- 确保DOM已更新:当我们在Vue中修改响应式数据后,Vue并不会立即更新DOM,而是将更新操作放入一个异步队列中。
nextTick
可以让我们在DOM更新完成后执行某些逻辑。 - 解决异步更新问题:由于Vue的响应式系统是基于异步更新机制的,直接在数据修改后访问DOM可能无法获取到最新的状态,而
nextTick
提供了一个可靠的时机。
示例代码:
import { nextTick } from 'vue';
const count = ref(0);
count.value++;
console.log(document.querySelector('#app').textContent); // 仍然是旧值
nextTick(() => {
console.log(document.querySelector('#app').textContent); // 新值已更新
});
2. nextTick
的原理
Vue的nextTick
实现依赖于JavaScript的事件循环机制和微任务/宏任务调度。以下是其核心原理:
(1)Vue 的异步更新机制
- Vue在检测到数据变化时,并不会立即更新DOM,而是将更新操作放入一个队列中。
- 这种设计是为了避免频繁的DOM操作(例如多次修改同一个数据时,只触发一次DO 更新),从而提升性能。
(2)微任务与宏任务
- Vue会利用浏览器的微任务或宏任务来调度 DOM 更新。
- 在Vue 3中,优先使用微任务,因为微任务的优先级更高,能够更快地执行。
(3)nextTick
的实现
- 当调用
nextTick
时,Vue会将传入的回调函数注册到一个微任务队列中。 - 当Vue完成当前的DOM更新后,微任务队列中的回调函数会被依次执行。
3. nextTick
的应用场景
在实际开发中,nextTick
常用于以下场景:
- DOM 操作:
- 当需要在数据更新后立即操作DO 时,可以使用
nextTick
确保DOM已更新。
count.value++; nextTick(() => { console.log(document.querySelector('#app').textContent); // 获取最新 DOM 内容 });
- 当需要在数据更新后立即操作DO 时,可以使用
- 第三方库集成:
- 当需要在 DOM 更新后初始化第三方库(如图表库、动画库等)时,可以使用
nextTick
。
- 当需要在 DOM 更新后初始化第三方库(如图表库、动画库等)时,可以使用
- 调试和日志记录:
- 在调试过程中,可以通过
nextTick
确保数据和DOM状态一致后再打印日志。
- 在调试过程中,可以通过
- 动态样式调整:
- 如果需要根据DOM的实际尺寸或其他属性动态调整样式,可以在
nextTick
中完成。
- 如果需要根据DOM的实际尺寸或其他属性动态调整样式,可以在
4. Vue 3 的改进
相比Vue 2,Vue 3的 nextTick`在实现上更加简洁高效:
- 基于 Composition API:Vue 3 的
nextTick
是一个独立的函数,可以直接从vue
包中导入,无需依赖 Vue 实例。
5. 总结
nextTick
是 Vue 中非常重要的一个工具,它解决了异步更新机制带来的 DOM 同步问题。通过理解其背后的原理(微任务/宏任务调度)和应用场景,我们可以在开发中更好地利用它来处理复杂的 DOM 操作和状态同步问题。
10. Vue template到render的过程
Vue 模板编译过程(Vue 3)
模板编译的核心流程为:template → AST → Render 函数。
在Vue 3中,编译过程主要由@vue/compiler-dom
包处理,流程如下:
1. 解析模板生成 AST(Abstract Syntax Tree)
将模板字符串解析为抽象语法树(AST),描述模板的结构和内容。
2. AST 转换(Transform)
- 目标:对 AST 进行优化和转换,注入Vue特有的逻辑(如指令、事件、条件渲染)。
- 关键步骤:
- 静态节点标记:标记静态节点(内容不会变化的节点),后续渲染时跳过更新。
- 指令处理:将Vue指令(如
v-if
、v-for
、v-bind
)转换为渲染逻辑。 - 事件处理:将
@click
等事件转换为on
对象的渲染逻辑。 - 插槽处理:解析插槽(
<slot>
)并生成对应的渲染逻辑。 - 优化标记:通过
patchFlag
标记节点的更新类型(如动态属性、动态文本),帮助运行时快速判断是否需要更新。
3. 代码生成(Generate)
- 目标:将AST转换为渲染函数(render function)的字符串形式。
- 实现细节:
- 生成渲染代码:遍历AST,生成以
h
(createVNode
)为核心的渲染函数字符串。 - 静态树提升:将静态节点(标记为静态的 AST 节点)提升到渲染函数外部,避免重复创建。
- 代码优化:合并静态文本、优化动态表达式,减少运行时计算量。
- 生成渲染代码:遍历AST,生成以
核心优化点(Vue 3 新增)
- 静态树提升:静态节点直接缓存为静态片段(
__staticTrees
),避免每次渲染重复创建。 - Patch Flags:通过标记节点的更新类型(如
patchFlag: 2
表示动态属性),让运行时直接跳过无需更新的节点。 - 指令与转换分离:将指令逻辑抽象为可复用的转换器(Transformer),提升扩展性。
- 编译时优化:提前分析模板结构,减少运行时计算量(如合并静态文本、优化动态表达式)。
11. 描述下Vue自定义指令
1. 基本概念
Vue自定义指令是一种用于直接操作DOM的底层功能,通过封装可复用的DOM行为,实现与组件逻辑解耦的交互(如表单自动聚焦、图片懒加载、按钮权限控制等)。
2. 使用场景
• DOM底层操作:如聚焦输入框、拖拽元素
• 全局行为封装:如权限控制指令v-permission
• 性能优化:如防抖/节流指令v-throttle
• 第三方库集成:如集成动画库或图表库
3. 创建方式
分为全局指令和局部指令:
// 全局指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 局部指令(组件内)
const MyComponent = {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
4. 指令生命周期钩子
Vue3 指令生命周期与组件生命周期对齐,包含以下钩子:
• created
:元素属性初始化前调用(无 DOM 访问权限)
• beforeMount
:元素插入 DOM 前调用
• mounted
:元素插入 DOM 后调用
• beforeUpdate
:元素更新前调用
• updated
:元素更新后调用
• beforeUnmount
:元素卸载前调用
• unmounted
:元素卸载后调用
5. 钩子函数参数
每个钩子接收以下参数:
• el
:指令绑定的DOM元素
• binding
:包含value
、oldValue
、arg
、modifiers 等属性 • **
vnode**:当前虚拟节点 • **
prevNode**:上一个虚拟节点(仅在
beforeUpdate和
updated`中可用)
6. 常见案例
案例 1:输入框自动聚焦
app.directive('focus', {
mounted(el) {
el.focus()
}
})
案例 2:按钮权限控制
app.directive('permission', {
mounted(el, binding) {
const { value: role } = binding
const userRole = 'admin'
if (role !== userRole) el.style.display = 'none'
}
})
7. 原理简析
• Vue自定义指令通过directive
API 注册,在模板编译阶段被解析为渲染函数中的指令对象。
• 底层通过withDirectives
函数将指令逻辑注入到虚拟节点的props
中,在DOM挂载/更新时触发对应钩子。
12. Vue的性能优化有哪些
编码阶段
v-if
和v-for
不能连用- 如果需要使用
v-for
给每项元素绑定事件时使用事件代理 - SPA页面采用
keep-alive
缓存组件 - 在更多的情况下,使用
v-if
替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 抽离公共文件
- sourceMap优化
用户体验
- 骨架屏
- PWA
- 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
13. 对SPA单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现HTML内容的变换,UI与用户的交互,避免页面的重新加载。
优点
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- 基于上面一点,SPA 相对对服务器压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
缺点
- 初次加载耗时多:为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CS 统一加载,部分页面按需加载
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
- SEO难度较大:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势
组件通信
感觉我很熟啊,不需要写了
算了,稍微写一下吧
父子:
- props
- emit
- ref和expose
- 3.4新增的defineModel 兄弟:
- provide/inject
- 父组件中转 祖孙:
- provide/inject
- props链式 论外:
- 全局状态管理库,如pinia
- 路由路径参数或者查询参数
- 本地存储
路由
1. Vue-Router的懒加载如何实现
非懒加载:
import List from '@/components/list.vue'
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
懒加载:
const List = () => import('@/components/list.vue')
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
2. 路由的hash和history模式的区别
hash模式
简介: hash模式是开发中默认的模式,它的URL带着一个#,例如:http://www.abc.com/#/vue,它的hash值就是#/vue。
特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
原理:hash模式的主要原理就是onhashchange()
事件
使用onhashchange()
事件的好处就是,在页面的hash值发生变化时,无需向后端发起请求,window
就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。
history模式
简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
特点: 当使用history模式时,URL就像这样:http://abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
API: history api可以分为两大部分,切换历史状态和修改历史状态:
- 修改历史状态:包括了HTML5 History Interface中新增的
pushState()
和replaceState()
方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。 - 切换历史状态:包括
forward()
、back()
、go()
三个方法,对应浏览器的前进,后退,跳转操作。
3. route
和router
的区别
route
是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数
router
是“路由实例”对象包括了路由的跳转方法,钩子函数等。
4. Vue-router路由钩子在生命周期的体现
Vue-Router导航守卫
有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。
为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的
全局路由钩子
vue-router全局有三个路由钩子;
router.beforeEach
全局前置守卫 进入路由之前router.beforeResolve
全局解析守卫(2.5.0+)在beforeRouteEnter
调用之后调用router.afterEach
全局后置钩子 进入路由之后
单个路由独享钩子
beforeEnter
如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next
{
path: '/',
name: 'login',
component: login,
beforeEnter: (to, from, next) => {
console.log('即将进入登录页面')
next()
}
}
虚拟DOM
1. Vue中key的作用
一、在 v-if
中使用 key
:强制重新渲染
1. 问题背景
当使用 v-if
切换相同类型的元素时,Vue默认会复用已有元素(出于性能考虑)。这种复用可能导致以下问题:
<template>
<!-- 切换登录/注册表单 -->
<form v-if="isLogin">
<input type="text" placeholder="用户名">
</form>
<form v-else>
<input type="text" placeholder="邮箱">
</form>
</template>
- 现象:从“登录”切换到“注册”时,输入框的
placeholder
会更新,但用户已输入的内容不会清空。 - 原因:Vue 复用了
<input>
元素,仅更新其属性,未重新渲染 DOM 节点。
2. 解决方案:添加 key
<template>
<form v-if="isLogin" key="login-form">
<input type="text" placeholder="用户名">
</form>
<form v-else key="register-form">
<input type="text" placeholder="邮箱">
</form>
</template>
- 效果:
key
的唯一性会强制Vu 销毁旧元素并创建新元素,彻底清除残留状态。 - 本质:破坏了Vue的“就地复用”策略,确保元素独立。
二、在v-for
中使用key
:高效更新列表
1. 问题背景
当使用v-for
渲染动态列表时,Vue默认采用“就地更新”策略:
<template>
<div v-for="item in list">{{ item.text }}</div>
</template>
- 现象:如果列表数据顺序变化(如排序、过滤),Vue会直接复用现有DOM元素,仅更新内容。
- 潜在问题:
- 状态错乱:若列表项包含组件或表单元素,复用可能导致内部状态未正确更新。
- 性能浪费:无法通过移动DOM优化渲染,可能触发不必要的子组件重新渲染。
2. 解决方案:添加key
<template>
<div v-for="item in list" :key="item.id">{{ item.text }}</div>
</template>
- 作用:
key
作为唯一标识符,帮助Vue跟踪每个节点的身份。 - 优化原理:
- 精准复用:当数据顺序变化时,Vue通过
key
识别节点,移动DOM元素而非重新创建。 - 避免状态错乱:组件或表单元素与正确的数据项保持绑定。
- 精准复用:当数据顺序变化时,Vue通过
3. 错误用法:用索引作为key
<template>
<!-- 不推荐! -->
<div v-for="(item, index) in list" :key="index"></div>
</template>
- 问题:当列表发生插入、删除或排序时,索引无法稳定对应数据项,导致:
- 组件状态混乱(如复选框选中错误项)。
- 子组件生命周期钩子被错误触发。
- 正确做法:使用数据中唯一的标识(如
id
)。
三、深入原理:key
如何影响Diff算法
Vue 的虚拟 DOM Diff 算法通过key
和标签类型判断节点是否可复用:
1. sameVNode
函数
在对比新旧节点时,Vue 会调用sameVNode
判断是否复用:
function sameVNode(a, b) {
return (
a.key === b.key && // key 相同
a.tag === b.tag // 标签类型相同
// ...其他条件(如命名空间等)
)
}
- 若
key
相同且标签一致,则复用DOM元素,仅更新属性。 - 若
key
不同,则销毁旧元素,创建新元素。
2. Key 对性能的影响
- 无 Key:Vue 会按顺序遍历新旧子节点,逐个对比(时间复杂度 O(n))。
- 有 Key:生成
{ key: node }
的映射表,直接通过key查找节点(时间复杂度O(1)),大幅减少比对次数。
四、总结:key
的最佳实践
场景 | 作用 | 推荐值 | 注意事项 |
---|---|---|---|
v-if 切换 |
强制重新渲染 | 唯一静态字符串 | 避免与其他元素 key 冲突 |
v-for 列表 |
精准追踪数据项 | 数据中的唯一标识(如 id ) |
不要用索引或随机值 |
- 核心规则:
key
应在同一父级下唯一且稳定,与数据身份强关联。 - 特殊场景:在
<transition>
或<keep-alive>
中,key
也可用于强制重新渲染组件。