《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
v-once
优化性能?v-once
是 Vue 的内置指令,用于标记元素或组件只渲染一次,后续即使数据变化也不会重新渲染,从而优化性能。
使用场景:
示例:
<p v-once>© 2023 Vue3 面试题集(内容固定)p>
<div v-once>
<h2>{{ initialTitle }}h2>
<p>{{ initialDescription }}p>
div>
注意:v-once
会阻止其子元素的响应式更新,因此仅适用于确知内容不会变化的场景,滥用可能导致数据更新后视图不刷新的问题。
toRef
与 toRefs
的区别及使用场景?toRef
和 toRefs
都是用于将响应式对象的属性转为 ref
对象的 API,以保持响应式特性,但适用场景不同:
toRef
:
ref
对象,与原对象保持关联。import { reactive, toRef } from 'vue';
const obj = reactive({ name: 'Vue3', age: 3 });
const nameRef = toRef(obj, 'name'); // 仅转换 name 属性
nameRef.value = 'Vue'; // 修改会同步到原对象,触发更新
toRefs
:
ref
对象,返回一个包含这些 ref
的普通对象。import { reactive, toRefs } from 'vue';
const obj = reactive({ name: 'Vue3', age: 3 });
const { name, age } = toRefs(obj); // 所有属性转为 ref
name.value = 'Vue'; // 同步更新原对象
age.value = 4; // 同步更新原对象
总结:toRef
用于单个属性,toRefs
用于多个属性,均用于解决响应式对象解构后丢失响应式的问题。
组件的动态切换指根据条件或状态渲染不同的组件,Vue3 中可通过以下方式实现:
使用
标签配合 :is
属性:
<template>
<component :is="currentComponent">component>
<button @click="currentComponent = ComponentA">显示Abutton>
<button @click="currentComponent = ComponentB">显示Bbutton>
template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const currentComponent = ref(ComponentA); // 初始显示 ComponentA
script>
通过 v-if
/v-else-if
/v-else
条件渲染:
<template>
<ComponentA v-if="type === 'A'">ComponentA>
<ComponentB v-else-if="type === 'B'">ComponentB>
<ComponentC v-else>ComponentC>
<button @click="type = 'A'">切换到Abutton>
<button @click="type = 'B'">切换到Bbutton>
template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
import ComponentC from './ComponentC.vue';
const type = ref('A');
script>
对比:
适用于组件类型较多、切换频繁的场景,更灵活。v-if
系列适用于条件简单、组件类型较少的场景,可读性更强。v-for
与 v-if
同时使用的注意事项?在 Vue 中,v-for
和 v-if
同时使用可能导致逻辑混淆和性能问题,需注意以下事项:
优先级问题:
v-for
的优先级高于 v-if
,即先循环渲染所有项,再对每个项执行 v-if
判断。
<li v-for="item in list" v-if="item.active" :key="item.id">
{{ item.name }}
li>
item.active
为 false
,v-for
仍会循环该 item,造成不必要的渲染开销。优化方案:
import { ref, computed } from 'vue';
const list = ref([/* 原始数据 */]);
const filteredList = computed(() => list.value.filter(item => item.active));
<li v-for="item in filteredList" :key="item.id">
{{ item.name }}
li>
v-if
:若需整体条件渲染(如列表是否显示),将 v-if
放在 v-for
外层。<ul v-if="showList">
<li v-for="item in list" :key="item.id">{{ item.name }}li>
ul>
避免逻辑混淆:同时使用时需明确目的(是过滤项还是控制整体显示),优先通过计算属性优化。
封装自定义表单控件需支持 v-model
双向绑定,使组件能像原生表单元素一样使用。Vue3 中实现方式如下:
示例:封装一个带校验的 Input 组件
使用自定义 Input 组件:
核心原理:通过 modelValue
接收值,update:modelValue
事件更新值,实现 v-model
双向绑定,同时支持自定义事件和校验逻辑。
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
runtime-core
与 runtime-dom
的区别?Vue3 的核心包被拆分为多个模块,@vue/runtime-core
和 @vue/runtime-dom
是其中两个核心模块,职责不同:
@vue/runtime-core
:
@vue/runtime-dom
:
runtime-core
,针对浏览器环境的运行时扩展,提供 DOM 相关的实现。class
、style
绑定的 DOM 实现)。关系:runtime-dom
依赖 runtime-core
,并在其基础上添加了浏览器特有的功能。开发 Web 应用时,通常引入的 vue
包已包含两者;开发跨平台应用时,可能直接使用 runtime-core
配合特定平台的运行时(如 @vue/runtime-mini-program
)。
provide
/inject
实现跨层级组件通信并保持响应式?provide
和 inject
用于跨层级组件通信(如祖父组件向孙子组件传递数据),结合响应式 API 可保持数据的响应式:
实现步骤:
祖先组件使用 provide
提供响应式数据:
import { provide, ref, reactive } from 'vue';
const count = ref(0); // ref 响应式数据
const user = reactive({ name: 'Vue3' }); // reactive 响应式对象
// 提供数据(key 为标识,value 为响应式数据)
provide('countKey', count);
provide('userKey', user);
后代组件使用 inject
接收数据并保持响应式:
import { inject } from 'vue';
// 接收响应式数据(第二个参数为默认值)
const count = inject('countKey', ref(0));
const user = inject('userKey', reactive({ name: '默认名称' }));
// 使用数据(修改会触发祖先组件及其他依赖组件的更新)
const increment = () => { count.value++; };
const changeName = () => { user.name = 'Vue'; };
传递修改方法(推荐):
// 祖先组件
const count = ref(0);
const incrementCount = () => { count.value++; };
provide('countKey', { count, incrementCount });
// 后代组件
const { count, incrementCount } = inject('countKey');
// 调用方法修改,而非直接修改 count.value
incrementCount();
注意:provide
传递的若为响应式数据(ref
/reactive
),inject
接收后仍保持响应式;若传递非响应式数据,修改后后代组件不会感知,因此需传递响应式数据以保持通信。
Vue3 的模板编译阶段进行了多项优化,减少运行时的虚拟 DOM 对比开销,提升渲染性能,主要策略如下:
静态提升(Static Hoisting):
<div>
<h1>静态标题h1>
<p>{{ dynamicText }}p>
div>
Patch Flag(补丁标记):
/* TEXT */
、/* CLASS */
),标记节点的动态部分(文本、类名、属性等)。TEXT
,更新时仅检查文本内容。缓存事件处理函数:
@click="handleClick"
)进行缓存,避免每次渲染创建新的函数实例,减少虚拟 DOM 的不必要更新。Block 树优化:
Block
(块),每个 Block
包含一组相邻的动态节点。Block
,进一步缩小对比范围。条件编译:
Proxy
)生成不同的编译结果,优化运行时代码。这些优化使 Vue3 的渲染性能相比 Vue2 提升约 55%(官方数据),尤其在大型应用中效果显著。
Suspense
结合异步组件和异步数据加载?Suspense
可同时处理异步组件和组件内部的异步数据加载,统一管理加载状态,提升用户体验:
实现步骤:
定义异步组件(包含异步数据加载):
{{ user.name }}
年龄:{{ user.age }}
在父组件中用 Suspense
包裹异步组件:
加载用户数据中...
工作流程:
Suspense
先显示 fallback
插槽的加载状态。defineAsyncComponent
)和组件内部的数据加载(await fetchUser()
)。Suspense
切换到 default
插槽,显示加载完成的内容。注意:若任一异步操作失败,需通过错误边界(onErrorCaptured
)捕获错误,避免应用崩溃。
ref
的实现原理?ref
是 Vue3 中用于创建响应式数据的核心 API,支持基本类型和对象,其实现原理如下:
包装基本类型:
string
、number
等),ref
将其包装为一个包含 value
属性的对象(RefImpl
实例)。value
属性通过 getter
和 setter
实现响应式:
getter
:访问 value
时,触发依赖收集(track
),记录使用该值的副作用函数。setter
:修改 value
时,触发依赖更新(trigger
),执行所有关联的副作用函数(如组件渲染)。处理对象类型:
ref
的是对象或数组,ref
会自动调用 reactive
将其转为响应式对象(Proxy
代理)。ref.value
指向该响应式对象,访问或修改对象的属性时,由 reactive
的 Proxy 机制处理响应式。自动解包:
ref
时,Vue 会自动解包(无需 .value
),直接访问值。reactive
对象中访问 ref
属性时,也会自动解包:const count = ref(0);
const obj = reactive({ count });
console.log(obj.count); // 0(自动解包,等价于 count.value)
简化伪代码:
class RefImpl {
constructor(value) {
// 若为对象,转为 reactive
this._value = isObject(value) ? reactive(value) : value;
}
get value() {
track(this, 'get', 'value'); // 收集依赖
return this._value;
}
set value(newValue) {
if (newValue !== this._value) {
this._value = isObject(newValue) ? reactive(newValue) : newValue;
trigger(this, 'set', 'value'); // 触发更新
}
}
}
function ref(value) {
return new RefImpl(value);
}
总结:ref
通过包装对象的 value
属性实现基本类型的响应式,通过 reactive
处理对象类型,同时支持自动解包以简化使用。
组件懒加载通过动态导入(import()
)实现,配合打包工具(如 Webpack、Vite)的代码分割功能,将组件代码拆分为单独的 chunk,按需加载,减少初始加载体积。
实现方式:
路由级懒加载(最常用):
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
// 动态导入组件,打包时会分割为单独的 chunk
const Home = () => import('../views/Home.vue');
const About = () => import('../views/About.vue');
// 更精细的分割:指定 chunk 名称(Webpack 语法)
const User = () => import(/* webpackChunkName: "user" */ '../views/User.vue');
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user', component: User }
];
const router = createRouter({ history: createWebHistory(), routes });
组件级懒加载(非路由组件):
import { defineAsyncComponent } from 'vue';
// 懒加载组件,支持加载状态和错误处理
const LazyComponent = defineAsyncComponent({
loader: () => import('./LazyComponent.vue'), // 动态导入
loadingComponent: Loading, // 加载中显示的组件
errorComponent: Error, // 加载失败显示的组件
delay: 200 // 延迟 200ms 显示加载组件(避免闪烁)
});
在模板中使用懒加载组件:
<template>
<button @click="showLazy = true">显示懒加载组件button>
<LazyComponent v-if="showLazy" />
template>
<script setup>
import { ref } from 'vue';
import LazyComponent from './LazyComponent'; // 导入懒加载组件
const showLazy = ref(false);
script>
优势:
注意:懒加载组件会增加用户交互时的加载延迟,需配合加载状态提示(如 defineAsyncComponent
的 loadingComponent
)优化体验。
Vue3 对虚拟 DOM 进行了重构和优化,相比 Vue2 有以下差异:
特性 | Vue3 虚拟 DOM | Vue2 虚拟 DOM |
---|---|---|
结构优化 | 采用扁平化的虚拟 DOM 结构,减少嵌套层级 | 嵌套层级较深,递归对比开销大 |
编译时优化 | 结合模板编译,生成带 Patch Flag 的虚拟节点 | 纯运行时对比,无编译时优化 |
对比范围 | 仅对比带 Patch Flag 的动态节点及标记的动态部分 | 全量对比所有节点,包括静态节点 |
性能提升 | 大型应用中渲染性能提升约 55%(官方数据) | 渲染性能随节点数量增加下降较明显 |
数组处理 | 优化数组更新,减少不必要的元素移动 | 数组更新时可能导致大量元素重新渲染 |
Tree-shaking | 支持 Tree-shaking,仅打包使用的虚拟 DOM 方法 | 虚拟 DOM 方法打包时无法剔除,体积较大 |
核心优化点:
示例:
// 动态文本节点被标记为 TEXT
createVNode('p', null, [createTextVNode(`${msg}`, 1 /* TEXT */)]);
TEXT
的节点的文本内容,忽略其他静态部分。v-slot
定义和使用插槽?v-slot
是 Vue 中用于定义和使用插槽的指令,替代了 Vue2 中的 slot
和 slot-scope
,Vue3 中使用方式如下:
默认插槽:
<template>
<div class="child">
<slot>slot>
div>
template>
<Child>
<template v-slot:default>
<p>这是默认插槽的内容p>
template>
Child>
<Child>
<p>这是默认插槽的内容p>
Child>
具名插槽(多个插槽区分):
<template>
<div>
<slot name="header">slot>
<slot>slot>
<slot name="footer">slot>
div>
template>
<Child>
<template v-slot:header>
<h1>标题h1>
template>
<p>正文内容(默认插槽)p>
<template v-slot:footer>
<p>版权信息p>
template>
Child>
作用域插槽(子组件向父组件传递数据):
<template>
<div>
<slot name="item" :data="user" :index="0">slot>
div>
template>
<script setup>
import { reactive } from 'vue';
const user = reactive({ name: 'Vue3' });
script>
<Child>
<template v-slot:item="slotProps">
<p>名称:{{ slotProps.data.name }}p>
<p>索引:{{ slotProps.index }}p>
template>
Child>
<template v-slot:item="{ data, index }">
<p>名称:{{ data.name }},索引:{{ index }}p>
template>
简化语法:v-slot:name
可缩写为 #name
,如 #header
等价于 v-slot:header
。
reactive
的实现原理?reactive
是 Vue3 中用于创建响应式对象的 API,基于 ES6 的 Proxy
实现,其核心原理如下:
Proxy 代理对象:
reactive
接收一个对象,返回该对象的 Proxy
代理实例。Proxy
可拦截对象的多种操作(get
、set
、deleteProperty
等),从而实现响应式。依赖收集(get
拦截):
obj.name
)时,get
拦截器被触发。track
函数,收集当前活跃的副作用函数(如组件渲染函数、watch
回调),建立属性与副作用函数的依赖关系。触发更新(set
/deleteProperty
拦截):
obj.name = 'new'
)或删除属性(delete obj.name
)时,set
或 deleteProperty
拦截器被触发。trigger
函数,找到该属性的所有依赖的副作用函数并执行,触发组件更新或回调函数执行。递归代理嵌套对象:
obj.user.name
)时,get
拦截器会检查该属性是否为对象,若是则递归调用 reactive
使其成为响应式对象(懒代理)。简化伪代码:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 递归代理嵌套对象
const value = Reflect.get(target, key, receiver);
if (isObject(value)) {
return reactive(value);
}
// 收集依赖
track(target, key);
return value;
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver);
if (value !== oldValue) {
Reflect.set(target, key, value, receiver);
// 触发更新
trigger(target, key);
}
return true;
},
deleteProperty(target, key) {
Reflect.deleteProperty(target, key);
// 触发更新
trigger(target, key);
return true;
}
});
}
注意:
reactive
仅支持对象和数组,不支持基本类型(基本类型需用 ref
)。reactive(obj) !== obj
),需使用代理对象才能触发响应式。基于路由的权限控制指根据用户权限动态显示或隐藏路由,阻止未授权用户访问受保护的页面。Vue3 中结合 Vue Router 实现方式如下:
定义路由元信息(meta
):
// router/index.js
const routes = [
{ path: '/login', component: Login },
{ path: '/403', component: Forbidden }, // 无权限页面
{
path: '/dashboard',
component: Dashboard,
meta: { requiresAuth: true, roles: ['admin', 'editor'] } // 需认证和特定角色
},
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true, roles: ['admin'] } // 仅 admin 可访问
}
];
全局前置守卫验证权限:
import { createRouter } from 'vue-router';
const router = createRouter({ /* 配置 */ });
router.beforeEach((to, from, next) => {
// 1. 无需认证的路由直接放行(如登录页)
if (!to.meta.requiresAuth) {
next();
return;
}
// 2. 需认证:检查用户是否登录
const isLogin = localStorage.getItem('token');
if (!isLogin) {
next('/login?redirect=' + to.path); // 未登录,跳转到登录页并记录目标路径
return;
}
// 3. 检查角色权限
const userRoles = JSON.parse(localStorage.getItem('roles')) || [];
const requiredRoles = to.meta.roles || [];
if (requiredRoles.length === 0 || requiredRoles.some(role => userRoles.includes(role))) {
next(); // 权限匹配,放行
} else {
next('/403'); // 无权限,跳转到 403 页面
}
});
动态生成路由(基于用户权限):
// 登录成功后
const addRoutesByRole = (roles) => {
const dynamicRoutes = roles.includes('admin')
? [/* admin 专属路由 */]
: [/* 普通用户路由 */];
dynamicRoutes.forEach(route => {
router.addRoute(route); // 动态添加路由
});
};
在组件中隐藏无权限的导航:
<template>
<nav>
<router-link to="/dashboard" v-if="hasPermission(['admin', 'editor'])">仪表盘router-link>
<router-link to="/admin" v-if="hasPermission(['admin'])">管理员页面router-link>
nav>
template>
<script setup>
const hasPermission = (requiredRoles) => {
const userRoles = JSON.parse(localStorage.getItem('roles')) || [];
return requiredRoles.some(role => userRoles.includes(role));
};
script>
核心逻辑:通过路由元信息标记权限要求,全局守卫验证登录状态和角色权限,动态控制路由访问和导航显示,确保未授权用户无法访问受保护资源。
文章序号 | vue3面试题120道 |
---|---|
1 | vue3 面试题及详细答案(01 - 15) |
2 | vue3 面试题及详细答案(16 - 30) |
3 | vue3 面试题及详细答案(31 - 45) |
4 | vue3 面试题及详细答案(46 - 60) |
5 | vue3 面试题及详细答案(61 - 75) |
6 | vue3 面试题及详细答案(76 - 90) |
7 | vue3 面试题及详细答案(91 - 105) |
8 | vue3 面试题及详细答案(106 - 120) |