《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 106. Vue3 中如何使用 `v-memo` 优化性能?
- 107. 请解释 Vue3 中 `customRef` 的作用及使用场景?
- 108. Vue3 中如何实现跨组件的状态管理?
- 109. 请描述 Vue3 中 `teleport` 的使用场景?
- 110. Vue3 中如何实现组件的过渡动画?
- 111. 请解释 Vue3 中 `unref` 的作用?
- 112. Vue3 中如何实现组件的错误边界?
- 113. 请描述 Vue3 中 `v-bind` 的修饰符及其作用?
- 114. Vue3 中如何优化大型列表渲染?
- 115. 请解释 Vue3 中 `markNonReactive` 的作用?
- 116. Vue3 中如何实现组件的单元测试?
- 117. 请描述 Vue3 中 `v-model` 的修饰符及其作用?
- 118. Vue3 中如何实现服务端渲染(SSR)?
- 119. 请解释 Vue3 中 `effectScope` 的作用及使用场景?
- 120. Vue3 中如何优化响应式系统的性能?
- 二、120道面试题目录列表
一、本文面试题目录
106. Vue3 中如何使用 v-memo
优化性能?
v-memo
是 Vue3 新增的指令,用于缓存虚拟 DOM 节点,避免不必要的渲染,提升性能。
基本用法:
<!-- 仅当 count 变化时才重新渲染 -->
<div v-memo="[count]">
<p>{{ count }}</p>
<ExpensiveComponent /> <!-- 复杂组件,避免重复渲染 -->
</div>
适用场景:
- 渲染大量列表且其中部分数据不变时,可缓存不变的列表项。
<!-- 仅当 item.id 或 item.name 变化时才重新渲染 -->
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">
{{ item.name }}
</div>
- 组件包含复杂计算或嵌套层级深,且依赖数据变化频率低。
注意事项:
- 需传入依赖数组(如
[a, b]
),数组中值变化时才重新渲染。 - 滥用可能导致内存占用增加,仅在性能敏感场景使用。
107. 请解释 Vue3 中 customRef
的作用及使用场景?
customRef
用于创建自定义响应式引用,可自定义依赖追踪和触发更新的逻辑,适用于:
- 防抖/节流输入:
import { customRef } from 'vue';
function useDebouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track(); // 收集依赖
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger(); // 触发更新
}, delay);
}
};
});
}
// 组件中使用
const searchText = useDebouncedRef('');
- 自定义缓存策略:
function useCachedRef(value) {
let cache = value;
return customRef((track, trigger) => {
return {
get() {
track();
return cache;
},
set(newValue) {
// 仅当值变化超过阈值时才更新
if (Math.abs(newValue - cache) > 10) {
cache = newValue;
trigger();
}
}
};
});
}
核心原理:
- 通过
customRef
返回一个包含get
和set
的对象。 track()
手动收集依赖,trigger()
手动触发更新。
108. Vue3 中如何实现跨组件的状态管理?
Vue3 中跨组件状态管理方案有:
- 小型项目:
provide
/inject
:
// 祖先组件
import { provide, ref } from 'vue';
const count = ref(0);
provide('count', count);
// 后代组件
import { inject } from 'vue';
const count = inject('count');
- 中型项目:
Pinia
(Vue 官方推荐):
import { defineStore } from 'pinia';
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++; }
}
});
// 组件中使用
const store = useCounterStore();
store.count;
store.increment();
- 大型项目:
Vuex 4
:
import { createStore } from 'vuex';
const store = createStore({
state: { count: 0 },
mutations: {
increment(state) { state.count++; }
}
});
// 组件中使用
const count = computed(() => store.state.count);
store.commit('increment');
- 简单状态:自定义事件总线:
// eventBus.js
import { createApp } from 'vue';
const eventBus = createApp({}).config.globalProperties;
// 发送事件
eventBus.$emit('update', data);
// 监听事件
eventBus.$on('update', (data) => { /* 处理 */ });
109. 请描述 Vue3 中 teleport
的使用场景?
teleport
用于将组件内容渲染到 DOM 中的其他位置,适用于:
- 模态框/弹窗:避免样式层级问题:
<teleport to="body">
<div class="modal" v-if="showModal">
<!-- 模态框内容 -->
</div>
</teleport>
- 固定位置组件:如悬浮按钮、通知栏:
<teleport to="#fixed-elements">
<button class="floating-btn">返回顶部</button>
</teleport>
- 与第三方 UI 库集成:
<!-- 将组件内容渲染到外部容器 -->
<teleport to=".third-party-container">
<MyComponent />
</teleport>
注意:
to
属性支持 CSS 选择器或 DOM 元素引用。- 内容仍保留组件实例关系,状态管理不受影响。
110. Vue3 中如何实现组件的过渡动画?
Vue3 提供 <Transition>
和 <TransitionGroup>
组件实现动画:
- 单个元素过渡:
<Transition name="fade">
<div v-if="show">Hello</div>
</Transition>
/* 淡入淡出效果 */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
- 列表过渡:
<TransitionGroup name="slide" tag="ul">
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</TransitionGroup>
/* 滑动效果 */
.slide-enter-active { animation: slide-in 0.5s; }
.slide-leave-active { animation: slide-in 0.5s reverse; }
@keyframes slide-in {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
- 自定义动画钩子:
<Transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<div v-if="show">动画元素</div>
</Transition>
const beforeEnter = (el) => {
el.style.opacity = 0;
};
const enter = (el, done) => {
setTimeout(() => {
el.style.opacity = 1;
done();
}, 0);
};
111. 请解释 Vue3 中 unref
的作用?
unref
是 Vue3 的工具函数,用于获取 ref
对象的值,自动处理 ref
或普通值:
import { ref, unref } from 'vue';
const count = ref(10);
console.log(unref(count)); // 10(等价于 count.value)
const num = 20;
console.log(unref(num)); // 20(普通值直接返回)
使用场景:
- 函数参数可能是
ref
或普通值时:
function add(a, b) {
return unref(a) + unref(b);
}
add(ref(1), 2); // 3
add(3, 4); // 7
- 简化模板中
ref
的使用:
const msg = ref('Hello');
const upperMsg = computed(() => unref(msg).toUpperCase());
112. Vue3 中如何实现组件的错误边界?
错误边界用于捕获子组件的运行时错误,避免应用崩溃,Vue3 中通过 onErrorCaptured
钩子实现:
import { onErrorCaptured } from 'vue';
export default {
setup() {
onErrorCaptured((err, instance, info) => {
// err:错误对象
// instance:抛出错误的组件实例
// info:错误信息(如生命周期钩子名称)
// 处理错误(如记录日志、显示错误界面)
console.error('捕获到错误:', err);
// 阻止错误向上传播
return true;
});
}
};
结合 Suspense
使用:
<Suspense>
<MyComponent />
<template #fallback>加载中...</template>
</Suspense>
// 全局错误捕获
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误捕获:', err);
};
113. 请描述 Vue3 中 v-bind
的修饰符及其作用?
Vue3 中 v-bind
支持以下修饰符:
.prop
:强制绑定为 DOM 属性而非特性:
<!-- 绑定为 DOM 的 innerHTML 属性 -->
<div v-bind:innerHTML.prop="htmlContent"></div>
.camel
:将 kebab-case 特性名转换为 camelCase:
<!-- 绑定为 myProp(而非 my-prop) -->
<MyComponent v-bind:my-prop.camel="value" />
.sync
:双向绑定语法糖(Vue3 推荐使用v-model
):
<!-- Vue2 写法 -->
<ChildComponent :title.sync="parentTitle" />
<!-- Vue3 等价写法 -->
<ChildComponent
:title="parentTitle"
@update:title="parentTitle = $event"
/>
114. Vue3 中如何优化大型列表渲染?
大型列表渲染优化策略:
- 虚拟列表:仅渲染可视区域内的元素:
// 使用第三方库如 vue-virtual-scroller
<RecycleScroller
class="items"
:items="list"
:item-size="32"
key-field="id"
>
<template #default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</RecycleScroller>
- 分批渲染:
const itemsPerBatch = 20;
const visibleItems = ref([]);
const loadMore = () => {
const start = visibleItems.value.length;
visibleItems.value = [
...visibleItems.value,
...list.slice(start, start + itemsPerBatch)
];
};
// 初始加载第一批
onMounted(() => {
loadMore();
});
- 列表项缓存:
<div v-for="item in list" :key="item.id" v-memo="[item.value]">
{{ item.value }}
</div>
- 减少 DOM 操作:
<!-- 避免频繁更新整个列表 -->
<MyComponent v-if="shouldUpdate" :items="items" />
115. 请解释 Vue3 中 markNonReactive
的作用?
markNonReactive
用于标记对象的属性为非响应式,避免被 reactive
代理:
import { reactive, markNonReactive } from 'vue';
const obj = reactive({
name: 'Vue3',
config: { /* 大型配置对象,无需响应式 */ }
});
// 标记 config 为非响应式
markNonReactive(obj.config);
适用场景:
- 大型静态数据(如配置、常量),避免响应式开销。
- 第三方库实例(如
new Chart()
),无需响应式追踪。
116. Vue3 中如何实现组件的单元测试?
Vue3 组件单元测试方案:
- 使用 Vitest + Vue Test Utils:
// Counter.vue
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
// Counter.spec.js
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
it('should increment count on click', () => {
const wrapper = mount(Counter);
wrapper.find('button').trigger('click');
expect(wrapper.text()).toBe('1');
});
});
- 测试 Composition API:
// useCounter.js
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
// useCounter.spec.js
import { describe, it, expect } from 'vitest';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('should increment count', () => {
const { count, increment } = useCounter();
increment();
expect(count.value).toBe(1);
});
});
117. 请描述 Vue3 中 v-model
的修饰符及其作用?
Vue3 中 v-model
支持以下修饰符:
.trim
:自动过滤输入首尾空格:
<input v-model.trim="message" />
.number
:将输入转换为数字类型:
<input v-model.number="age" type="number" />
.lazy
:将input
事件改为change
事件触发更新:
<input v-model.lazy="message" />
- 自定义修饰符:
// 注册全局修饰符
app.config.globalProperties.$customModifiers = {
uppercase: (value) => value.toUpperCase()
};
// 使用
<input v-model.uppercase="message" />
118. Vue3 中如何实现服务端渲染(SSR)?
Vue3 SSR 实现方案:
- 使用 Vite +
@vue/server-renderer
:
// server.js
import { createSSRApp } from 'vue';
import { renderToString } from '@vue/server-renderer';
import App from './App.vue';
const server = require('http').createServer(async (req, res) => {
const app = createSSRApp(App);
const html = await renderToString(app);
res.end(`
<!DOCTYPE html>
<html>
<body>
<div id="app">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`);
});
- 使用 Nuxt3(官方推荐框架):
npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev
- 关键注意事项:
- 避免在
setup
中使用浏览器专属 API(如document
)。 - 使用
onBeforeMount
或onMounted
替代setup
中的副作用。 - 通过
process.server
区分客户端/服务端环境。
119. 请解释 Vue3 中 effectScope
的作用及使用场景?
effectScope
用于创建一个作用域,捕获其中的响应式副作用(如计算属性、watch
),便于统一管理:
- 手动控制副作用:
import { effectScope, ref, computed } from 'vue';
const scope = effectScope();
scope.run(() => {
const count = ref(0);
const double = computed(() => count.value * 2);
// 可通过 scope.stop() 停止所有副作用
});
// 停止所有副作用
scope.stop();
- 插件/库开发:
// 插件中使用
export function createMyPlugin() {
const scope = effectScope(true); // detached: true 独立作用域
scope.run(() => {
// 插件逻辑
});
return {
install(app) {
app.provide('myPlugin', { scope });
}
};
}
- 组件卸载后保留副作用:
const scope = effectScope(true);
scope.run(() => {
watch(source, () => { /* 即使组件卸载仍执行 */ });
});
120. Vue3 中如何优化响应式系统的性能?
优化响应式系统性能的策略:
- 避免过度响应式:
// 使用 readonly 包装无需修改的数据
const config = readonly({
apiUrl: 'https://api.example.com',
timeout: 5000
});
- 冻结大型静态对象:
const largeData = Object.freeze([/* 大型数据 */]);
const state = reactive({
data: largeData // 不会被转为响应式
});
- 使用 shallowReactive/shallowRef:
// 仅第一层属性响应式
const state = shallowReactive({
list: [/* 大型列表 */]
});
// 修改 list 需替换整个数组
state.list = [...state.list, newItem];
- 批量更新:
import { nextTick } from 'vue';
const state = reactive({
count: 0,
text: ''
});
// 批量修改,避免多次触发更新
const updateState = async () => {
state.count++;
state.text = 'Loading...';
await nextTick(); // 等待所有更新完成
// 执行后续操作
};