《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 76. Vue3 中如何实现组件的按需加载且指定加载状态?
- 77. 请解释 Vue3 中的 “响应式丢失” 问题及解决方案?
- 78. Vue3 中如何使用 `keep-alive` 缓存组件及控制缓存范围?
- 79. 请比较 Vue3 中的 `computed` 与 `watch` 的使用场景?
- 80. 请描述 Vue3 中 `setup` 函数与 `script setup` 的区别?
- 81. Vue3 中如何实现组件的递归调用?
- 82. 请解释 Vue3 中 `markRaw` 和 `shallowRef`/`shallowReactive` 的使用场景?
- 83. Vue3 中如何实现组件的强制更新?
- 84. 请解释 Vue3 中的 `v-bind` 与 `v-model` 的区别?
- 85. Vue3 中如何处理组件的异步数据加载?
- 86. 请描述 Vue3 中自定义 hooks 的实现及使用场景?
- 87. Vue3 中如何实现路由参数的监听?
- 88. 请解释 Vue3 中的 `app.config.globalProperties` 的作用及使用方式?
- 89. Vue3 中如何处理跨域请求?
- 90. 请对比 Vue3 的 Composition API 与 React Hooks 的异同?
- 二、120道面试题目录列表
一、本文面试题目录
76. Vue3 中如何实现组件的按需加载且指定加载状态?
在 Vue3 中,可通过 defineAsyncComponent
实现组件的按需加载,并配合配置指定加载状态和错误处理。具体如下:
import { defineAsyncComponent } from 'vue';
import LoadingComponent from './LoadingComponent.vue';
import ErrorComponent from './ErrorComponent.vue';
// 按需加载组件,指定加载状态和错误状态
const AsyncComponent = defineAsyncComponent({
// 加载组件的函数,返回 Promise
loader: () => import('./TargetComponent.vue'),
// 加载过程中显示的组件
loadingComponent: LoadingComponent,
// 加载失败时显示的组件
errorComponent: ErrorComponent,
// 延迟多久后显示加载组件(默认 200ms),避免短暂加载导致的闪烁
delay: 300,
// 超时时间,超过此时长视为加载失败(单位:ms)
timeout: 5000
});
使用时,直接在模板中像普通组件一样使用 AsyncComponent
即可。这种方式能减少初始加载的资源体积,提升应用启动速度,同时通过加载状态提示优化用户体验。
77. 请解释 Vue3 中的 “响应式丢失” 问题及解决方案?
“响应式丢失” 指当对响应式对象进行解构、展开或赋值给新变量时,新变量可能失去响应式特性的问题。这是因为响应式对象的响应式依赖是基于属性访问的,解构等操作会破坏这种依赖关系。
示例:
import { reactive } from 'vue';
const obj = reactive({ name: 'Vue3', age: 3 });
// 解构后,name 和 age 失去响应式
const { name, age } = obj;
name = 'Vue'; // 修改不会触发视图更新
// 展开到新对象,新对象非响应式
const newObj = { ...obj };
newObj.age = 4; // 修改不会触发视图更新
解决方案:
- 使用
toRefs
解构:将响应式对象的属性转为ref
对象,保持响应式。import { reactive, toRefs } from 'vue'; const obj = reactive({ name: 'Vue3', age: 3 }); const { name, age } = toRefs(obj); // name 和 age 是 ref 对象 name.value = 'Vue'; // 会触发更新
- 直接访问原对象属性:避免解构,通过原响应式对象访问属性。
- 对新对象重新创建响应式:若需将展开的对象转为响应式,使用
reactive
或ref
重新包装。const newObj = reactive({ ...obj }); // newObj 是响应式对象
78. Vue3 中如何使用 keep-alive
缓存组件及控制缓存范围?
keep-alive
是 Vue 的内置组件,用于缓存不常变化的组件,避免重复渲染,提升性能。Vue3 中使用方式如下:
基本用法:
<!-- 缓存包裹的组件 -->
<keep-alive>
<ComponentA v-if="showA"></ComponentA>
<ComponentB v-else></ComponentB>
</keep-alive>
控制缓存范围:
include
:仅缓存名称匹配的组件(组件需通过name
选项定义名称)。<!-- 仅缓存 ComponentA 和 ComponentB --> <keep-alive include="ComponentA,ComponentB"> <router-view></router-view> </keep-alive>
exclude
:不缓存名称匹配的组件。<!-- 不缓存 ComponentC --> <keep-alive exclude="ComponentC"> <router-view></router-view> </keep-alive>
max
:限制缓存组件的最大数量,超出时移除最早缓存的组件。<!-- 最多缓存 3 个组件 --> <keep-alive max="3"> <router-view></router-view> </keep-alive>
配合路由缓存:通常用于 router-view
,缓存路由组件,避免切换路由时重复加载。
79. 请比较 Vue3 中的 computed
与 watch
的使用场景?
computed
和 watch
都用于响应式数据的变化处理,但适用场景不同:
特性 | computed | watch |
---|---|---|
核心功能 | 基于依赖数据计算衍生值,具有缓存 | 监听数据变化,执行自定义逻辑 |
缓存机制 | 依赖不变时,多次访问返回缓存结果 | 无缓存,数据变化即执行回调 |
适用场景 | 1. 从现有数据计算新值(如格式化数据) 2. 依赖多个数据的衍生值 3. 需要缓存计算结果的场景 | 1. 数据变化时执行异步操作(如接口请求) 2. 数据变化时执行复杂逻辑(如日志记录) 3. 需要获取新旧值对比的场景 |
写法 | 函数式,返回计算结果 | 需指定监听源和回调函数 |
示例:
// computed:计算总价(依赖数量和单价)
const quantity = ref(2);
const price = ref(10);
const total = computed(() => quantity.value * price.value);
// watch:监听总价变化,发送请求
watch(total, (newTotal) => {
api.updateTotal(newTotal); // 异步操作
});
80. 请描述 Vue3 中 setup
函数与 script setup
的区别?
setup
函数和 <script setup>
都是 Vue3 中使用 Composition API 的方式,但存在以下区别:
特性 | setup 函数 | <script setup> |
---|---|---|
写法位置 | 在组件选项中定义(export default { setup() {} } ) | 作为 <script> 标签的属性(<script setup> ) |
返回值处理 | 需返回对象,对象属性才能在模板中使用 | 无需返回,变量和函数直接在模板中可用 |
组件注册 | 需在 components 选项中注册局部组件 | 导入组件后自动注册,无需显式声明 |
Props/Emits | 通过参数 props 和 context.emit 处理 | 通过 defineProps 和 defineEmits 编译宏处理 |
代码简洁度 | 相对繁琐,需手动返回和注册组件 | 更简洁,减少模板代码,开发效率更高 |
类型支持 | 需手动指定类型,类型推断较弱 | 原生支持 TypeScript,类型推断更友好 |
生命周期使用 | 需导入钩子函数并在 setup 中调用 | 直接导入钩子函数调用,与代码逻辑融合更自然 |
示例:
setup
函数:import { ref } from 'vue'; import Child from './Child.vue'; export default { components: { Child }, // 需注册组件 setup(props) { const count = ref(0); return { count }; // 需返回才能在模板使用 } };
<script setup>
:<script setup> import { ref } from 'vue'; import Child from './Child.vue'; // 自动注册 const count = ref(0); // 直接在模板中使用 </script>
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
81. Vue3 中如何实现组件的递归调用?
组件递归调用指组件在自身模板中调用自己,适用于树形结构、菜单等场景。Vue3 中实现方式如下:
-
定义组件并指定
name
选项(递归调用需依赖组件名称):<!-- Tree.vue --> <template> <div class="tree-node"> <p>{{ node.label }}</p> <!-- 递归调用自身,渲染子节点 --> <Tree v-for="child in node.children" :key="child.id" :node="child" v-if="node.children && node.children.length" /> </div> </template> <script> export default { name: 'Tree', // 必须指定 name,用于递归调用 props: { node: { type: Object, required: true } } }; </script>
-
使用组件:
<template> <Tree :node="rootNode" /> </template> <script setup> import Tree from './Tree.vue'; const rootNode = { label: '根节点', children: [ { label: '子节点1', children: [{ label: '孙节点1' }] }, { label: '子节点2' } ] }; </script>
注意:需确保递归有终止条件(如 v-if
判断子节点是否存在),避免无限递归导致栈溢出。
82. 请解释 Vue3 中 markRaw
和 shallowRef
/shallowReactive
的使用场景?
markRaw
、shallowRef
和 shallowReactive
是 Vue3 中用于控制响应式深度的 API,适用于特定性能优化场景:
-
markRaw
:- 功能:标记一个对象为“非响应式”,使其永远不会被 Proxy 代理。
- 场景:
- 存储无需响应式的大型对象(如第三方库实例、图表配置)。
- 避免对不可变数据进行响应式处理(节省性能)。
- 示例:
import { markRaw, reactive } from 'vue'; const chart = markRaw(new Chart()); // 非响应式 const obj = reactive({ chart }); // obj 是响应式,但 chart 仍为非响应式
-
shallowRef
:- 功能:只对
.value
进行响应式处理,不递归处理内部对象。 - 场景:
- 存储大型对象,仅需监听对象引用变化,无需监听内部属性。
- 优化性能,避免深层代理的开销。
- 示例:
import { shallowRef } from 'vue'; const data = shallowRef({ list: [] }); data.value.list.push(1); // 内部属性变化不触发更新 data.value = { list: [1] }; // 引用变化触发更新
- 功能:只对
-
shallowReactive
:- 功能:只对对象的第一层属性进行响应式处理,不递归代理嵌套对象。
- 场景:
- 处理结构固定的浅层对象(如表单的第一层字段)。
- 已知嵌套对象无需响应式,减少代理开销。
- 示例:
import { shallowReactive } from 'vue'; const obj = shallowReactive({ a: 1, b: { c: 2 } }); obj.a = 2; // 第一层属性变化,触发更新 obj.b.c = 3; // 嵌套属性变化,不触发更新
83. Vue3 中如何实现组件的强制更新?
在 Vue 中,响应式数据变化会自动触发组件更新,但某些特殊场景(如非响应式数据变化)可能需要强制更新组件。Vue3 中实现强制更新的方式如下:
-
使用
forceUpdate
方法:- 通过
getCurrentInstance
获取组件实例,调用forceUpdate
方法。
import { getCurrentInstance } from 'vue'; const instance = getCurrentInstance(); // 强制更新 const handleForceUpdate = () => { instance.ctx.$forceUpdate(); };
- 通过
-
通过
ref
触发更新:- 定义一个无用的
ref
变量,修改其值触发更新(间接强制更新)。
import { ref } from 'vue'; const trigger = ref(0); // 强制更新 const handleForceUpdate = () => { trigger.value++; // 修改 ref 值,触发组件更新 };
- 定义一个无用的
注意:强制更新会跳过响应式依赖检查,直接触发组件重新渲染,可能导致性能问题,应尽量避免使用,优先通过规范的响应式数据驱动更新。
84. 请解释 Vue3 中的 v-bind
与 v-model
的区别?
v-bind
和 v-model
都是 Vue 中用于数据绑定的指令,但功能和使用场景不同:
特性 | v-bind | v-model |
---|---|---|
绑定方向 | 单向绑定(从数据到视图) | 双向绑定(数据到视图,视图到数据) |
语法糖 | 无,直接绑定属性值 | 是 v-bind:value + v-on:input 的语法糖 |
使用场景 | 绑定 HTML 属性、组件 props 等 | 表单元素(input、select 等)、自定义组件 |
事件处理 | 需手动绑定事件(如 @input )更新数据 | 自动处理输入事件,同步数据 |
修饰符支持 | 支持 .prop 、.camel 等属性修饰符 | 支持 .trim 、.number 等表单修饰符 |
示例:
v-bind
单向绑定:<!-- 绑定输入框 value(单向) --> <input v-bind:value="name" @input="name = $event.target.value" /> <!-- 绑定组件 props --> <ChildComponent :msg="message" />
v-model
双向绑定:<!-- 表单双向绑定(等价于上面的 v-bind + @input) --> <input v-model="name" /> <!-- 自定义组件双向绑定 --> <ChildComponent v-model="message" />
总结:v-bind
适用于单向数据传递,v-model
适用于需要用户输入交互并同步数据的场景(如表单)。
85. Vue3 中如何处理组件的异步数据加载?
Vue3 中处理组件异步数据加载的常用方式有以下几种:
-
在
onMounted
中加载:- 组件挂载后发起请求,适用于简单场景。
import { ref, onMounted } from 'vue'; const data = ref(null); onMounted(async () => { data.value = await api.fetchData(); // 异步请求 });
-
使用
watch
监听依赖加载:- 当依赖数据(如路由参数)变化时重新加载数据。
import { ref, watch } from 'vue'; import { useRoute } from 'vue-router'; const route = useRoute(); const data = ref(null); // 监听路由参数变化 watch(() => route.params.id, async (id) => { data.value = await api.fetchDataById(id); }, { immediate: true }); // 初始执行一次
-
结合
Suspense
和异步组件:- 组件
setup
函数返回 Promise,配合Suspense
显示加载状态。
// AsyncDataComponent.vue <script setup> const data = await api.fetchData(); // setup 中使用 await </script> // 父组件使用 <Suspense> <AsyncDataComponent /> <template #fallback>加载中...</template> </Suspense>
- 组件
-
使用组合式函数封装:
- 将异步逻辑封装为可复用函数,如
useFetch
。
// useFetch.js import { ref, onMounted } from 'vue'; export function useFetch(url) { const data = ref(null); const loading = ref(true); onMounted(async () => { data.value = await fetch(url).then(res => res.json()); loading.value = false; }); return { data, loading }; } // 组件中使用 <script setup> import { useFetch } from './useFetch'; const { data, loading } = useFetch('/api/data'); </script>
- 将异步逻辑封装为可复用函数,如
86. 请描述 Vue3 中自定义 hooks 的实现及使用场景?
自定义 hooks 是基于 Composition API 封装的可复用逻辑函数,命名通常以 use
开头,用于抽取组件中的共性逻辑,提升代码复用性。
实现方式:
// useCounter.js(自定义 hook)
import { ref, computed, watch } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
// 监听 count 变化,可传入回调
const watchCount = (callback) => {
watch(count, callback);
};
return { count, doubleCount, increment, decrement, watchCount };
}
使用场景:
- 通用逻辑复用:如计数器、表单处理、数据请求等。
// 组件中使用 <script setup> import { useCounter } from './useCounter'; const { count, increment } = useCounter(10); // 初始值 10 </script>
- 复杂逻辑拆分:将组件中的复杂逻辑拆分为多个 hooks,使代码结构清晰。
// 组件中组合多个 hooks import { useFetch } from './useFetch'; import { useForm } from './useForm'; const { data } = useFetch('/api/data'); const { form, validate } = useForm();
- 跨组件状态共享:结合
provide/inject
,实现跨组件的逻辑复用。
优势:相比 mixins,自定义 hooks 不会导致命名冲突,逻辑更清晰,且类型支持更好。
87. Vue3 中如何实现路由参数的监听?
在 Vue3 中,可通过以下方式监听路由参数的变化(如动态路由 :id
的变化):
-
使用
watch
监听路由参数:import { watch } from 'vue'; import { useRoute } from 'vue-router'; const route = useRoute(); // 监听路由参数变化 watch( () => route.params, // 监听 params 对象 (newParams, oldParams) => { console.log('参数变化:', newParams.id); // 执行参数变化后的逻辑(如重新加载数据) loadData(newParams.id); }, { immediate: true } // 初始加载时执行一次 );
-
监听单个路由参数:
// 只监听 id 参数 watch( () => route.params.id, (newId) => { console.log('id 变化:', newId); loadData(newId); }, { immediate: true } );
-
使用组件内导航守卫
onBeforeRouteUpdate
:import { onBeforeRouteUpdate } from 'vue-router'; onBeforeRouteUpdate((to, from) => { // 路由更新时触发(同一路由,参数变化) console.log('新参数:', to.params.id); loadData(to.params.id); });
注意:路由参数变化时,组件不会重新创建,因此 onMounted
钩子不会重新执行,需通过上述方式监听参数变化并处理逻辑。
88. 请解释 Vue3 中的 app.config.globalProperties
的作用及使用方式?
app.config.globalProperties
用于在 Vue3 应用中注册全局属性或方法,使所有组件都能访问这些属性或方法,类似 Vue2 中的 Vue.prototype
。
作用:
- 注册全局通用的工具函数、常量或第三方库实例(如
$api
、$utils
)。 - 避免在每个组件中重复导入,简化代码。
使用方式:
-
注册全局属性:
// main.js import { createApp } from 'vue'; import App from './App.vue'; import api from './api'; // 接口工具 const app = createApp(App); // 注册全局属性 app.config.globalProperties.$api = api; app.config.globalProperties.$version = '1.0.0'; app.mount('#app');
-
在组件中访问:
- 在模板中直接访问:
<template> <div>{{ $version }}</div> </template>
- 在
<script setup>
中通过getCurrentInstance
访问:import { getCurrentInstance } from 'vue'; const instance = getCurrentInstance(); const api = instance.appContext.config.globalProperties.$api; // 使用 api.fetchData();
- 在模板中直接访问:
注意:
- 全局属性可能导致组件与全局依赖耦合,过度使用会降低组件可维护性。
- 在 TypeScript 中,需扩展全局类型声明才能获得类型提示。
89. Vue3 中如何处理跨域请求?
Vue3 中处理跨域请求的方式与前端框架无关,主要通过以下两种方式解决:
-
开发环境:配置 Vite 代理:
在vite.config.js
中配置代理,将前端请求转发到后端服务器,避免浏览器跨域限制。// vite.config.js import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], server: { proxy: { // 匹配以 /api 开头的请求 '/api': { target: 'http://localhost:3000', // 后端接口地址 changeOrigin: true, // 允许跨域 rewrite: (path) => path.replace(/^\/api/, '') // 移除路径中的 /api } } } });
前端请求示例:
// 实际请求会被代理到 http://localhost:3000/data fetch('/api/data').then(res => res.json());
-
生产环境:后端配置 CORS:
后端服务器通过设置Access-Control-Allow-*
响应头,允许跨域请求。// 后端(Node.js Express 示例) const express = require('express'); const app = express(); // 允许所有域名跨域(生产环境需指定具体域名) app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); next(); });
注意:开发环境代理仅用于本地调试,生产环境必须依赖后端 CORS 配置。
90. 请对比 Vue3 的 Composition API 与 React Hooks 的异同?
Vue3 的 Composition API 和 React Hooks 都是用于逻辑复用和组织的函数式 API,存在以下异同:
特性 | Composition API | React Hooks |
---|---|---|
核心目的 | 解决 Vue2 选项式 API 的逻辑复用问题 | 解决 React class 组件的逻辑复用问题 |
依赖追踪 | 自动依赖追踪(基于 Proxy 响应式系统) | 手动依赖数组(useEffect([deps]) ) |
生命周期映射 | onMounted 对应 componentDidMount 等 | useEffect 统一处理生命周期逻辑 |
状态定义 | ref (基本类型)、reactive (对象) | useState (所有类型,返回 [state, setter]) |
缓存机制 | computed 自动缓存计算结果 | useMemo 、useCallback 手动缓存 |
规则限制 | 无严格调用顺序限制(可在条件中使用) | 必须在函数顶层调用,不能在条件中使用 |
代码复用 | 自定义 hooks(useXXX 函数) | 自定义 hooks(useXXX 函数) |
上下文使用 | provide /inject | useContext |
示例:
- Composition API 实现计数器:
import { ref, computed, onMounted } from 'vue'; function useCounter() { const count = ref(0); const double = computed(() => count.value * 2); const increment = () => count.value++; onMounted(() => console.log('计数器初始化')); return { count, double, increment }; }
- React Hooks 实现计数器:
import { useState, useMemo, useEffect } from 'react'; function useCounter() { const [count, setCount] = useState(0); const double = useMemo(() => count * 2, [count]); const increment = () => setCount(c => c + 1); useEffect(() => { console.log('计数器初始化'); }, []); // 空依赖模拟 componentDidMount return { count, double, increment }; }
总结:两者核心思想一致(函数式逻辑复用),但实现细节因框架特性(响应式系统、渲染机制)不同而有差异。