Appearance
Vue.js 基础面试题
1. Vue 基础概念
问题:Vue.js 的核心特性是什么?
答案:
核心特性:
- 响应式数据绑定:
javascript
const { createApp, ref } = Vue;
createApp({
setup() {
const count = ref(0);
return { count };
}
}).mount('#app');
// 修改数据
count.value++;- 组件化:
javascript
// 定义组件
const MyComponent = {
template: '<div>{{ message }}</div>',
data() {
return {
message: 'Hello Vue!'
};
}
};
// 使用组件
createApp({
components: {
MyComponent
}
}).mount('#app');- 指令系统:
javascript
// v-bind
<div v-bind:href="url"></div>
<div :href="url"></div>
// v-on
<div v-on:click="doSomething"></div>
<div @click="doSomething"></div>
// v-model
<input v-model="message">
// v-if/v-else
<div v-if="show">Visible</div>
<div v-else>Hidden</div>
// v-for
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>- 虚拟 DOM:
- Vue 使用虚拟 DOM 来提高渲染性能
- 通过 diff 算法比较新旧虚拟 DOM
- 只更新需要变化的部分
2. Vue 生命周期
问题:Vue 的生命周期有哪些?
答案:
生命周期钩子:
javascript
export default {
// 创建阶段
beforeCreate() {
console.log('beforeCreate');
// 实例刚被创建,数据观测和事件配置之前
},
created() {
console.log('created');
// 实例创建完成,可以访问 data、methods 等
},
beforeMount() {
console.log('beforeMount');
// 挂载开始之前
},
mounted() {
console.log('mounted');
// 实例挂载完成,可以访问 DOM
},
// 更新阶段
beforeUpdate() {
console.log('beforeUpdate');
// 数据更新,DOM 重新渲染之前
},
updated() {
console.log('updated');
// DOM 重新渲染完成
},
// 销毁阶段
beforeUnmount() {
console.log('beforeUnmount');
// 实例销毁之前
},
unmounted() {
console.log('unmounted');
// 实例销毁完成
}
};Composition API 生命周期:
javascript
import { onMounted, onUpdated, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('mounted');
});
onUpdated(() => {
console.log('updated');
});
onUnmounted(() => {
console.log('unmounted');
});
}
};3. 组件通信
问题:Vue 组件之间如何通信?
答案:
父子通信:
javascript
// 父组件
<template>
<ChildComponent
:message="parentMessage"
@update="handleUpdate"
/>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
data() {
return {
parentMessage: 'Hello from parent'
};
},
methods: {
handleUpdate(newMessage) {
console.log('Received:', newMessage);
}
}
};
</script>
// 子组件
<template>
<div>
<p>{{ message }}</p>
<button @click="sendUpdate">Send Update</button>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true
}
},
methods: {
sendUpdate() {
this.$emit('update', 'Hello from child');
}
}
};
</script>兄弟组件通信:
javascript
// 事件总线
// eventBus.js
import { createApp } from 'vue';
const eventBus = createApp({});
export default eventBus;
// 组件 A
import eventBus from './eventBus';
export default {
methods: {
sendMessage() {
eventBus.$emit('message', 'Hello');
}
}
};
// 组件 B
import eventBus from './eventBus';
export default {
mounted() {
eventBus.$on('message', (msg) => {
console.log('Received:', msg);
});
},
beforeUnmount() {
eventBus.$off('message');
}
};跨层级通信(Provide/Inject):
javascript
// 祖先组件
export default {
provide() {
return {
theme: 'dark',
updateTheme: this.updateTheme
};
},
methods: {
updateTheme(newTheme) {
this.theme = newTheme;
}
}
};
// 后代组件
export default {
inject: ['theme', 'updateTheme'],
methods: {
changeTheme() {
this.updateTheme('light');
}
}
};4. Vuex 状态管理
问题:Vuex 如何使用?
答案:
基本使用:
javascript
// store.js
import { createStore } from 'vuex';
export default createStore({
state: {
count: 0,
user: null
},
mutations: {
increment(state) {
state.count++;
},
setUser(state, user) {
state.user = user;
}
},
actions: {
async fetchUser({ commit }) {
const user = await api.getUser();
commit('setUser', user);
}
},
getters: {
doubleCount: state => state.count * 2,
isAuthenticated: state => !!state.user
}
});
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
const app = createApp(App);
app.use(store);
app.mount('#app');
// 组件中使用
export default {
computed: {
count() {
return this.$store.state.count;
},
doubleCount() {
return this.$store.getters.doubleCount;
}
},
methods: {
increment() {
this.$store.commit('increment');
},
fetchUser() {
this.$store.dispatch('fetchUser');
}
}
};模块化:
javascript
// modules/user.js
export default {
namespaced: true,
state: {
user: null
},
mutations: {
setUser(state, user) {
state.user = user;
}
},
actions: {
async fetchUser({ commit }) {
const user = await api.getUser();
commit('setUser', user);
}
}
};
// store.js
import user from './modules/user';
export default createStore({
modules: {
user
}
});
// 组件中使用
export default {
computed: {
user() {
return this.$store.state.user.user;
}
},
methods: {
fetchUser() {
this.$store.dispatch('user/fetchUser');
}
}
};5. Vue Router
问题:Vue Router 如何使用?
答案:
基本配置:
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');路由导航:
javascript
// 声明式导航
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'User', params: { id: 123 } }">User</router-link>
// 编程式导航
import { useRouter } from 'vue-router';
export default {
setup() {
const router = useRouter();
const goToHome = () => {
router.push('/');
};
const goToUser = (id) => {
router.push({ name: 'User', params: { id } });
};
const goBack = () => {
router.back();
};
return { goToHome, goToUser, goBack };
}
};路由守卫:
javascript
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
// 全局后置钩子
router.afterEach((to, from) => {
document.title = to.meta.title || 'My App';
});
// 路由独享守卫
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/');
}
}
}
];
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
next();
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
next();
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
next();
}
};6. Composition API
问题:Vue 3 的 Composition API 如何使用?
答案:
基本使用:
javascript
import { ref, reactive, computed, watch } from 'vue';
export default {
setup() {
// ref - 基本类型响应式
const count = ref(0);
// reactive - 对象类型响应式
const user = reactive({
name: 'John',
age: 30
});
// computed - 计算属性
const doubleCount = computed(() => count.value * 2);
// watch - 监听
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// 方法
const increment = () => {
count.value++;
};
const updateName = (name) => {
user.name = name;
};
// 返回给模板使用
return {
count,
user,
doubleCount,
increment,
updateName
};
}
};setup 语法糖:
javascript
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>生命周期:
javascript
import { onMounted, onUpdated, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('Component mounted');
});
onUpdated(() => {
console.log('Component updated');
});
onUnmounted(() => {
console.log('Component unmounted');
});
}
};7. 响应式原理
问题:Vue 的响应式原理是什么?
答案:
Vue 2 响应式原理:
javascript
// 简化版响应式实现
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
dep.notify();
}
});
}
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Watcher {
constructor(vm, key, cb) {
Dep.target = this;
this.cb = cb;
this.vm = vm;
this.key = key;
this.value = vm[key];
Dep.target = null;
}
update() {
const newValue = this.vm[this.key];
if (this.value !== newValue) {
this.value = newValue;
this.cb.call(this.vm, newValue);
}
}
}Vue 3 响应式原理:
javascript
// 使用 Proxy 实现响应式
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
});
}
// 依赖收集
const targetMap = new WeakMap();
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}8. 虚拟 DOM 和 Diff 算法
问题:Vue 的虚拟 DOM 和 Diff 算法是如何工作的?
答案:
虚拟 DOM:
javascript
// 虚拟 DOM 节点结构
function h(tag, props, children) {
return { tag, props, children };
}
// 创建虚拟 DOM
const vnode = h('div', { id: 'app' }, [
h('p', {}, 'Hello'),
h('p', {}, 'World')
]);
// 渲染为真实 DOM
function render(vnode) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const el = document.createElement(vnode.tag);
// 设置属性
if (vnode.props) {
Object.entries(vnode.props).forEach(([key, value]) => {
el.setAttribute(key, value);
});
}
// 渲染子节点
if (vnode.children) {
vnode.children.forEach(child => {
el.appendChild(render(child));
});
}
return el;
}Diff 算法:
javascript
// 简化版 Diff 算法
function patch(oldVnode, newVnode) {
// 节点类型不同,直接替换
if (oldVnode.tag !== newVnode.tag) {
const newEl = render(newVnode);
oldVnode.el.parentNode.replaceChild(newEl, oldVnode.el);
return;
}
const el = newVnode.el = oldVnode.el;
// 比较属性
if (oldVnode.props !== newVnode.props) {
updateProps(el, oldVnode.props, newVnode.props);
}
// 比较子节点
if (oldVnode.children !== newVnode.children) {
updateChildren(el, oldVnode.children, newVnode.children);
}
}
function updateChildren(el, oldChildren, newChildren) {
// 简单的 Diff 实现
const oldLen = oldChildren.length;
const newLen = newChildren.length;
const maxLen = Math.max(oldLen, newLen);
for (let i = 0; i < maxLen; i++) {
if (i < oldLen && i < newLen) {
patch(oldChildren[i], newChildren[i]);
} else if (i < newLen) {
el.appendChild(render(newChildren[i]));
} else {
el.removeChild(el.childNodes[i]);
}
}
}9. 组件优化
问题:如何优化 Vue 组件性能?
答案:
v-if vs v-show:
javascript
// v-if - 条件渲染(适合不频繁切换)
<div v-if="show">Content</div>
// v-show - CSS 切换(适合频繁切换)
<div v-show="show">Content</div>计算属性缓存:
javascript
export default {
data() {
return {
items: [1, 2, 3, 4, 5]
};
},
computed: {
// 计算属性有缓存,只在依赖变化时重新计算
evenItems() {
return this.items.filter(item => item % 2 === 0);
}
},
methods: {
// 方法每次调用都会重新计算
getEvenItems() {
return this.items.filter(item => item % 2 === 0);
}
}
};v-for 使用 key:
javascript
// 使用唯一 key 提高渲染性能
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>组件懒加载:
javascript
const routes = [
{
path: '/about',
component: () => import('../views/About.vue')
}
];keep-alive 缓存:
javascript
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>10. Vue 3 新特性
问题:Vue 3 有哪些新特性?
答案:
Composition API:
javascript
import { ref, reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
name: 'John'
});
// 解构响应式对象
return {
...toRefs(state)
};
}
};Teleport:
javascript
<template>
<button @click="showModal = true">Show Modal</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<p>Modal content</p>
<button @click="showModal = false">Close</button>
</div>
</teleport>
</template>Suspense:
javascript
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>Fragments:
javascript
<template>
<!-- 支持多个根节点 -->
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>
</template>Tree-shaking:
javascript
// Vue 3 模块化,支持 Tree-shaking
import { ref, computed } from 'vue';