《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux... 。

文章目录
- 一、本文面试题目录
-
-
- [61. Vue3 中如何实现组件的异步加载?](#61. Vue3 中如何实现组件的异步加载?)
- [62. 什么是 Vue3 的组合式 API 中的"依赖收集"?](#62. 什么是 Vue3 的组合式 API 中的“依赖收集”?)
- [63. Vue3 中如何使用 Teleport 组件?](#63. Vue3 中如何使用 Teleport 组件?)
- [64. Vue3 与 Vue2 的生命周期钩子有哪些区别?](#64. Vue3 与 Vue2 的生命周期钩子有哪些区别?)
- [65. Vue3 中 `v-model` 的实现原理是什么?](#65. Vue3 中
v-model
的实现原理是什么?) - [66. Vue3 中的 `ref` 和 `reactive` 有什么性能差异?](#66. Vue3 中的
ref
和reactive
有什么性能差异?) - [67. 如何在 Vue3 中实现全局状态管理(不使用 Vuex/Pinia)?](#67. 如何在 Vue3 中实现全局状态管理(不使用 Vuex/Pinia)?)
- [68. Vue3 中 `watch` 和 `watchEffect` 的区别是什么?](#68. Vue3 中
watch
和watchEffect
的区别是什么?) - [69. Vue3 中如何处理组件的错误边界?](#69. Vue3 中如何处理组件的错误边界?)
- [70. 什么是 Vue3 的"渲染函数"?如何使用?](#70. 什么是 Vue3 的“渲染函数”?如何使用?)
- [71. Vue3 中如何实现跨组件通信?](#71. Vue3 中如何实现跨组件通信?)
- [72. Vue3 中的 `defineProps` 和 `defineEmits` 有什么作用?](#72. Vue3 中的
defineProps
和defineEmits
有什么作用?) - [73. Vue3 中如何优化长列表渲染性能?](#73. Vue3 中如何优化长列表渲染性能?)
- [74. 什么是 Vue3 的"编译器宏"?有哪些常用宏?](#74. 什么是 Vue3 的“编译器宏”?有哪些常用宏?)
- [75. Vue3 中如何实现路由守卫?](#75. Vue3 中如何实现路由守卫?)
-
一、本文面试题目录
61. Vue3 中如何实现组件的异步加载?
在 Vue3 中,可通过 defineAsyncComponent
函数实现组件的异步加载,它能将组件加载推迟到需要时进行,提升初始加载速度。
-
基本用法:
javascriptimport { defineAsyncComponent } from 'vue' // 异步加载组件 const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
-
高级配置(可指定加载状态、错误处理等):
javascriptconst AsyncComponent = defineAsyncComponent({ loader: () => import('./AsyncComponent.vue'), loadingComponent: LoadingComponent, // 加载中显示的组件 errorComponent: ErrorComponent, // 加载失败显示的组件 delay: 200, // 延迟多久后显示加载组件(默认 200ms) timeout: 3000 // 超时时间,超过则显示错误组件 })
62. 什么是 Vue3 的组合式 API 中的"依赖收集"?
依赖收集是 Vue3 响应式系统的核心机制,指追踪组件渲染过程中使用的响应式数据,当这些数据变化时,自动触发相关组件或副作用的重新执行。
- 实现原理:
- 执行副作用函数(如组件渲染函数、
watch
回调)时,Vue 会将当前副作用函数设为"活跃状态"。 - 访问响应式数据时,数据的
getter
会将活跃副作用函数添加到自身的依赖列表中。 - 当数据变化触发
setter
时,会遍历依赖列表,执行所有关联的副作用函数。
- 执行副作用函数(如组件渲染函数、
63. Vue3 中如何使用 Teleport 组件?
Teleport
( teleport 译为"传送")用于将组件的 DOM 结构"传送"到页面的指定位置,解决嵌套组件样式或层级冲突问题(如模态框、弹窗)。
-
基本用法:
vue<template> <button @click="showModal = true">打开弹窗</button> <Teleport to="body"> <!-- 将内容传送到 body 标签下 --> <div v-if="showModal" class="modal"> <p>这是弹窗内容</p> <button @click="showModal = false">关闭</button> </div> </Teleport> </template> <script setup> import { ref } from 'vue' const showModal = ref(false) </script>
-
注意:
to
属性值可以是 CSS 选择器或 DOM 元素,且传送的内容仍受当前组件的响应式数据控制。
64. Vue3 与 Vue2 的生命周期钩子有哪些区别?
Vue3 保留了大部分 Vue2 的生命周期概念,但在组合式 API 中采用函数式写法,且部分钩子名称有调整:
Vue2 选项式 API | Vue3 组合式 API | 说明 |
---|---|---|
beforeCreate |
无(可在 setup 中替代) |
初始化前,setup 执行时机类似 |
created |
无(可在 setup 中替代) |
初始化后,setup 执行时机类似 |
beforeMount |
onBeforeMount |
挂载前触发 |
mounted |
onMounted |
挂载后触发 |
beforeUpdate |
onBeforeUpdate |
更新前触发 |
updated |
onUpdated |
更新后触发 |
beforeDestroy |
onBeforeUnmount |
卸载前触发(名称调整) |
destroyed |
onUnmounted |
卸载后触发(名称调整) |
errorCaptured |
onErrorCaptured |
捕获子组件错误时触发 |
- 新增钩子:
onRenderTracked
(渲染追踪时触发)、onRenderTriggered
(渲染触发时触发),用于调试响应式依赖。
65. Vue3 中 v-model
的实现原理是什么?
Vue3 中 v-model
是语法糖,本质是通过props 和事件 实现父子组件数据双向绑定,相比 Vue2 更灵活,支持自定义修饰符和多个 v-model
。
-
基本原理:
- 父组件使用
v-model:propName="value"
时,等价于:propName="value" @update:propName="value = $event"
。 - 子组件通过
defineProps
接收propName
,并通过defineEmits
触发update:propName
事件传递新值。
- 父组件使用
-
示例:
vue<!-- 父组件 --> <ChildComponent v-model:count="num" /> <!-- 子组件 --> <template> <button @click="handleClick">点击</button> </template> <script setup> const props = defineProps(['count']) const emit = defineEmits(['update:count']) const handleClick = () => { emit('update:count', props.count + 1) // 触发更新事件 } </script>
66. Vue3 中的 ref
和 reactive
有什么性能差异?
ref
和 reactive
都是创建响应式数据的 API,但实现机制不同,性能表现有差异:
reactive
:- 基于 Proxy 实现,直接代理对象,对对象的属性访问/修改进行拦截。
- 性能:对于大型对象,初始化时需要递归代理所有属性,可能有轻微性能损耗;但访问属性时无需额外解包,效率较高。
ref
:- 用于基本类型或单个值,内部通过
{ value: ... }
包装,访问/修改需通过.value
。 - 性能:初始化轻量,但每次访问/修改都需经过
.value
的 getter/setter,频繁操作时可能比reactive
略慢。
- 用于基本类型或单个值,内部通过
- 建议:
- 基本类型或单个值用
ref
,对象/数组用reactive
。 - 频繁操作的响应式数据优先考虑
reactive
(如复杂表单)。
- 基本类型或单个值用
67. 如何在 Vue3 中实现全局状态管理(不使用 Vuex/Pinia)?
可通过响应式数据 + 全局注入实现简单全局状态管理,适用于小型项目:
-
创建全局响应式数据:
javascript// store.js import { reactive, readonly } from 'vue' const state = reactive({ userInfo: null, theme: 'light' }) // 对外暴露只读版本,防止直接修改 const globalState = readonly(state) // 定义修改状态的方法 const mutations = { setUserInfo(info) { state.userInfo = info }, toggleTheme() { state.theme = state.theme === 'light' ? 'dark' : 'light' } } export { globalState, mutations }
-
在组件中使用:
vue<script setup> import { globalState, mutations } from './store.js' // 读取状态 console.log(globalState.theme) // 修改状态 mutations.setUserInfo({ name: 'Vue3' }) </script>
- 缺点:缺乏 Vuex/Pinia 的模块化、DevTools 追踪、中间件等功能,大型项目仍建议使用 Pinia。
68. Vue3 中 watch
和 watchEffect
的区别是什么?
watch
和 watchEffect
都是监听响应式数据变化的 API,但用法和场景不同:
特性 | watch |
watchEffect |
---|---|---|
依赖指定 | 需明确指定监听的数据源(如 watch(refA, ...) ) |
自动收集依赖(函数内使用的响应式数据均会被监听) |
初始执行 | 默认不执行,需通过 immediate: true 开启 |
默认初始执行一次 |
回调参数 | 可获取新旧值((newVal, oldVal) => {} ) |
无参数,仅执行副作用 |
适用场景 | 需明确监听特定数据,或需要新旧值对比 | 依赖不明确,或需要初始执行的副作用(如数据加载) |
-
示例:
javascriptconst count = ref(0) // watch:明确监听 count watch(count, (newVal, oldVal) => { console.log('count变化:', newVal, oldVal) }) // watchEffect:自动监听内部使用的 count watchEffect(() => { console.log('count当前值:', count.value) })
69. Vue3 中如何处理组件的错误边界?
Vue3 中可通过 onErrorCaptured
钩子或自定义错误边界组件捕获子组件的错误,防止错误扩散导致整个应用崩溃:
-
方法1:使用
onErrorCaptured
钩子(组件内局部捕获):vue<script setup> import { onErrorCaptured, ref } from 'vue' const hasError = ref(false) onErrorCaptured((err, instance, info) => { console.error('捕获到错误:', err, info) hasError.value = true return true // 阻止错误继续向上传播 }) </script>
-
方法2:自定义错误边界组件(全局复用):
vue<!-- ErrorBoundary.vue --> <template> <slot v-if="!hasError" /> <div v-else>发生错误:{{ error.message }}</div> </template> <script setup> import { ref, onErrorCaptured } from 'vue' const hasError = ref(false) const error = ref(null) onErrorCaptured((err) => { hasError.value = true error.value = err return true }) </script>
-
使用错误边界组件:
vue<ErrorBoundary> <ChildComponent /> <!-- 子组件的错误会被捕获 --> </ErrorBoundary>
70. 什么是 Vue3 的"渲染函数"?如何使用?
渲染函数是用 JavaScript 代码描述组件渲染内容的函数,相比模板更灵活,适用于动态生成复杂 DOM 结构的场景。Vue3 中通过 h
函数(createVNode
的别名)创建虚拟 DOM 节点。
-
基本用法:
vue<script setup> import { h } from 'vue' // 定义渲染函数组件 const MyComponent = (props) => { return h('div', { class: 'my-component' }, [ h('h1', `Hello ${props.name}`), h('p', '这是渲染函数生成的内容') ]) } MyComponent.props = ['name'] // 声明 props </script> <template> <MyComponent name="Vue3" /> </template>
-
与模板的关系:模板最终会被编译为渲染函数,渲染函数是 Vue 内部渲染的底层实现。
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
71. Vue3 中如何实现跨组件通信?
Vue3 提供多种跨组件通信方式,适用于不同场景:
-
Props / Emits :父子组件通信,父传子用
props
,子传父用emits
。 -
Provide / Inject :祖孙组件通信,祖先通过
provide
提供数据,后代通过inject
接收。javascript// 祖先组件 import { provide } from 'vue' provide('theme', 'dark') // 后代组件 import { inject } from 'vue' const theme = inject('theme', 'light') // 第二个参数为默认值
-
全局状态管理:如 Pinia、Vuex,适用于任意组件间共享状态。
-
事件总线(Event Bus) :通过
mitt
库实现(Vue3 移除了内置$on/$emit
):javascript// bus.js import mitt from 'mitt' export const bus = mitt() // 组件A发送事件 bus.emit('user-updated', userInfo) // 组件B接收事件 bus.on('user-updated', (info) => { ... })
72. Vue3 中的 defineProps
和 defineEmits
有什么作用?
defineProps
和 defineEmits
是 Vue3 <script setup>
语法中用于声明组件 props 和自定义事件的编译宏,无需导入即可使用:
-
defineProps
:声明组件接收的 props,支持类型校验和默认值。javascript// 基础用法 const props = defineProps(['name', 'age']) // 带类型和默认值 const props = defineProps({ name: { type: String, required: true }, age: { type: Number, default: 18 } })
-
defineEmits
:声明组件可触发的自定义事件,支持类型校验。javascript// 基础用法 const emit = defineEmits(['change', 'submit']) // 带类型校验 const emit = defineEmits({ change: (value) => typeof value === 'string', // 验证事件参数 submit: () => true // 无需参数 }) // 触发事件 emit('change', 'new value')
-
特点:在
<script setup>
中自动具备类型推断,且返回的props
是响应式的(只读),emit
是触发事件的函数。
73. Vue3 中如何优化长列表渲染性能?
长列表(如 1000+ 条数据)渲染可能导致性能问题,可通过以下方式优化:
-
虚拟滚动 :只渲染可视区域内的列表项,通过
vue-virtual-scroller
等库实现。vue<template> <RecycleScroller :items="longList" :item-size="50" class="scroller" > <template v-slot="{ item }"> <div class="list-item">{{ item.text }}</div> </template> </RecycleScroller> </template>
-
懒加载:监听滚动事件,当列表滚动到一定位置时再加载更多数据。
-
避免响应式数据冗余 :长列表数据若无需响应式,可使用
markRaw
标记为非响应式。javascriptimport { markRaw } from 'vue' const longList = markRaw([/* 大量数据 */]) // 关闭响应式
-
使用
v-memo
:缓存列表项,仅当依赖变化时重新渲染。vue<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]"> {{ item.name }} </div>
74. 什么是 Vue3 的"编译器宏"?有哪些常用宏?
编译器宏是 Vue3 中仅在 <script setup>
或 setup()
函数中生效的特殊函数,由 Vue 编译器处理,无需导入即可使用,用于简化组件逻辑:
- 常用编译器宏:
-
defineProps
:声明组件 props。 -
defineEmits
:声明组件自定义事件。 -
defineExpose
:暴露组件内部属性/方法,供父组件通过ref
访问。javascriptconst count = ref(0) const increment = () => count.value++ defineExpose({ count, increment }) // 父组件可通过 ref 访问
-
withDefaults
:为defineProps
提供更灵活的默认值配置(支持函数返回默认值)。javascriptconst props = withDefaults(defineProps({ list: { type: Array }, config: { type: Object } }), { list: () => [], // 函数返回默认值(避免复用同一引用) config: () => ({ theme: 'light' }) })
-
75. Vue3 中如何实现路由守卫?
Vue3 中路由守卫的用法与 Vue2 类似,但需结合 Vue Router 4+ 的 API,常用守卫包括:
-
全局守卫 :
javascript// router/index.js import { createRouter } from 'vue-router' const router = createRouter(/* 配置 */) // 全局前置守卫(跳转前触发) router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLogin) { next('/login') // 未登录则跳转到登录页 } else { next() // 允许跳转 } }) // 全局后置守卫(跳转后触发) router.afterEach((to, from) => { document.title = to.meta.title || '默认标题' })
-
路由独享守卫 (在路由配置中定义):
javascriptconst routes = [ { path: '/admin', component: Admin, beforeEnter: (to, from, next) => { // 仅当前路由生效 if (!isAdmin) next('/forbidden') else next() } } ]
-
组件内守卫 :
vue<script setup> import { onBeforeRouteEnter, onBeforeRouteLeave } from 'vue-router' // 进入组件前触发(此时组件实例未创建,无法访问 this) onBeforeRouteEnter((to, from, next) => { next(vm => { // 通过 vm 访问组件实例 vm.fetchData() }) }) // 离开组件前触发 onBeforeRouteLeave((to, from, next) => { if (confirm('确定离开吗?')) next() else next(false) // 取消跳转 }) </script>