Vue
一、Vue 基础与 MVVM 模式
1. Vue 核心特性与设计模式
问题:Vue 的核心特性有哪些?Vue 的特点是什么?Vue 与 React 的区别是什么?什么是 MVVM 模式?MVVM 的组成部分有哪些?Vue 如何实现 MVVM?
答案:
Vue 核心特性:
- 响应式数据绑定:数据变化自动更新视图
- 组件系统:可复用、可组合的 UI 组件
- 模板语法:声明式渲染,类似 HTML 的模板语法
- 虚拟 DOM:高效更新真实 DOM
- 单文件组件:.vue 文件封装模板、脚本、样式
- 过渡动画系统:内置过渡和动画支持
- 路由与状态管理:Vue Router、Vuex 官方库
Vue vs React 对比表:
| 特性 | Vue | React |
|---|---|---|
| 模板语法 | HTML-based 模板 | JSX |
| 学习曲线 | 平缓,中文文档完善 | 较陡峭 |
| 数据绑定 | 双向数据绑定(v-model) | 单向数据流 |
| 状态管理 | Vuex(官方) | Redux/MobX(社区) |
| 组件通信 | props/emit、provide/inject、事件总线 | props、上下文、状态管理 |
| 生态系统 | 官方维护路由、状态管理 | 社区驱动,选择多样 |
| 构建工具 | Vue CLI | Create React App |
| TypeScript | 良好支持 | 优秀支持 |
MVVM 模式:
- Model:数据模型,包含业务数据和逻辑
- View:视图层,用户界面展示
- ViewModel:视图模型,连接 View 和 Model,负责数据绑定和命令绑定
Vue 的 MVVM 实现:
- 数据劫持:通过 Object.defineProperty 或 Proxy 实现响应式
- 依赖收集:在 getter 中收集依赖(Watcher)
- 派发更新:在 setter 中通知依赖更新
- 编译模板:将模板编译为渲染函数
- 虚拟 DOM:生成虚拟节点,通过 Diff 算法更新真实 DOM
补充说明:
- Vue 采用渐进式框架设计,可根据需求逐步引入功能
- MVVM 模式实现数据与视图的分离,提高代码可维护性
- Vue 3 使用 Proxy 替代 Object.defineProperty,性能更好
二、Vue 实例与生命周期
2. Vue 实例创建与生命周期钩子
问题:什么是 Vue 实例?如何创建 Vue 实例?Vue 实例的选项有哪些?什么是 Vue 生命周期?生命周期钩子有哪些?父子组件生命周期执行顺序是什么?
答案:
Vue 实例创建:
// 创建 Vue 实例
const app = new Vue({
el: '#app', // 挂载元素
data: { // 响应式数据
message: 'Hello Vue!'
},
methods: { // 方法
sayHello() {
console.log(this.message);
}
},
computed: { // 计算属性
reversedMessage() {
return this.message.split('').reverse().join('');
}
},
watch: { // 侦听器
message(newVal, oldVal) {
console.log('消息变化:', oldVal, '→', newVal);
}
}
});
Vue 实例核心选项:
- data:响应式数据对象
- methods:方法集合
- computed:计算属性(基于依赖缓存)
- watch:侦听属性变化
- props:接收父组件传递的数据
- components:注册局部组件
- directives:注册局部指令
- filters:注册过滤器
- mixins:混入对象
- extends:继承其他组件选项
Vue 生命周期钩子:
-
创建阶段:
beforeCreate:实例初始化,数据观测和事件配置之前created:实例创建完成,数据观测、属性和方法已配置,但 DOM 未生成
-
挂载阶段:
beforeMount:模板编译/挂载之前mounted:实例已挂载到 DOM,可访问 DOM 元素
-
更新阶段:
beforeUpdate:数据更新,DOM 重新渲染之前updated:数据更新,DOM 重新渲染完成
-
销毁阶段:
beforeDestroy:实例销毁之前,可进行清理操作destroyed:实例销毁完成,所有指令解绑,事件监听器移除
父子组件生命周期执行顺序:
父 beforeCreate → 父 created → 父 beforeMount
→ 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted
→ 父 mounted
更新时顺序:
父 beforeUpdate
→ 子 beforeUpdate → 子 updated
→ 父 updated
销毁时顺序:
父 beforeDestroy
→ 子 beforeDestroy → 子 destroyed
→ 父 destroyed
生命周期钩子应用场景:
- created:发起异步请求、初始化数据
- mounted:操作 DOM、初始化第三方库
- beforeDestroy:清除定时器、解绑事件、取消请求
- updated:数据更新后执行 DOM 操作
三、Vue 模板语法与指令
3. 模板语法与常用指令
问题:什么是 Vue 模板语法?什么是插值表达式?什么是指令?常用的指令有哪些?v-bind、v-on、v-model、v-if、v-show、v-for 指令如何使用?v-if 与 v-show 的区别是什么?
答案:
模板语法:
<!-- 插值表达式 -->
<div>{{ message }}</div>
<!-- JavaScript 表达式 -->
<div>{{ message.split('').reverse().join('') }}</div>
<div>{{ ok ? 'YES' : 'NO' }}</div>
<!-- 原始 HTML -->
<div v-html="rawHtml"></div>
<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div> <!-- 简写 -->
常用指令:
- v-bind:动态绑定属性
<img :src="imageSrc" :alt="imageAlt">
<button :disabled="isDisabled">按钮</button>
- v-on:绑定事件
<button @click="handleClick">点击</button> <!-- 简写 -->
<button v-on:click="handleClick">点击</button>
<input @input="handleInput">
- v-model:双向数据绑定(表单元素)
<input v-model="message" placeholder="输入">
<textarea v-model="text"></textarea>
<select v-model="selected">
<option value="A">选项A</option>
<option value="B">选项B</option>
</select>
- v-if / v-else / v-else-if:条件渲染
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>
- v-show:条件显示(通过 display 控制)
<div v-show="isVisible">显示内容</div>
- v-for:列表渲染
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
<li v-for="(value, key) in object">
{{ key }}: {{ value }}
</li>
v-if vs v-show 对比:
| 特性 | v-if | v-show |
|---|---|---|
| 渲染方式 | 条件为 false 时不渲染 DOM | 始终渲染,通过 display 控制显示 |
| 切换开销 | 高(销毁/重建组件) | 低(仅切换 CSS 属性) |
| 初始渲染 | 条件为 false 时不渲染 | 无论条件都渲染 |
| 适用场景 | 条件很少改变,切换不频繁 | 频繁切换显示/隐藏 |
| 与 v-for | 优先级高于 v-for | 优先级低于 v-for |
指令修饰符:
<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止冒泡</button>
<button @click.prevent="handleSubmit">阻止默认行为</button>
<button @click.once="handleOnce">只触发一次</button>
<!-- 按键修饰符 -->
<input @keyup.enter="submit">
<input @keyup.13="submit">
<!-- 表单修饰符 -->
<input v-model.lazy="message"> <!-- 输入完成后更新 -->
<input v-model.number="age"> <!-- 转为数字 -->
<input v-model.trim="username"> <!-- 去除首尾空格 -->
自定义指令:
// 全局注册
Vue.directive('focus', {
inserted: function(el) {
el.focus();
}
});
// 局部注册
directives: {
focus: {
inserted: function(el) {
el.focus();
}
}
}
// 使用
<input v-focus>
补充说明:
- 插值表达式支持 JavaScript 表达式,但不支持语句和控制流
- 指令的值可以是表达式,v-for 需要提供唯一的 key
- v-model 是语法糖,等价于
:value="message" @input="message = $event.target.value"
四、计算属性、侦听器与方法
4. 计算属性与侦听器的区别与应用
问题:什么是计算属性?计算属性与方法的区别是什么?计算属性与侦听器的区别是什么?计算属性的缓存机制是什么?什么是侦听器?侦听器的深度监听如何实现?
答案:
计算属性:
- 基于依赖的响应式数据进行计算
- 具有缓存机制,依赖不变时直接返回缓存值
- 声明为函数,但作为属性访问
computed: {
// 基本用法
reversedMessage() {
return this.message.split('').reverse().join('');
},
// 计算属性 setter(可写计算属性)
fullName: {
get() {
return this.firstName + ' ' + this.lastName;
},
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1] || '';
}
}
}
计算属性 vs 方法:
- 计算属性:基于依赖缓存,依赖不变时不会重新计算
- 方法:每次调用都会重新计算,无缓存
// 计算属性(缓存)
computed: {
now() {
return Date.now(); // ❌ 错误:不依赖响应式数据,不会更新
},
reversedMessage() {
return this.message.split('').reverse().join(''); // ✅ 正确
}
}
// 方法(无缓存)
methods: {
getReversedMessage() {
return this.message.split('').reverse().join('');
}
}
// 使用
<div>{{ reversedMessage }}</div> <!-- 计算属性 -->
<div>{{ getReversedMessage() }}</div> <!-- 方法 -->
侦听器:
- 观察响应式数据的变化,执行异步或开销较大的操作
- 适用于数据变化时执行副作用操作
watch: {
// 基本用法
message(newVal, oldVal) {
console.log('消息变化:', oldVal, '→', newVal);
},
// 深度监听对象
deepObject: {
handler(newVal, oldVal) {
console.log('对象变化');
},
deep: true, // 深度监听
immediate: true // 立即执行一次
},
// 监听对象属性
'user.name': function(newVal, oldVal) {
console.log('用户名变化');
},
// 监听数组
items: {
handler(newVal, oldVal) {
console.log('数组变化');
},
deep: true // 数组需要深度监听
}
}
计算属性 vs 侦听器对比表:
| 特性 | 计算属性 | 侦听器 |
|---|---|---|
| 用途 | 计算派生数据 | 响应数据变化执行操作 |
| 缓存 | 有缓存,依赖不变不重新计算 | 无缓存,每次变化都执行 |
| 同步/异步 | 必须同步返回结果 | 可执行异步操作 |
| 代码组织 | 声明式,关注结果 | 命令式,关注过程 |
| 适用场景 | 模板中使用的派生数据 | 数据变化时执行副作用(请求、DOM操作) |
深度监听实现:
watch: {
// 方法1:使用 deep 选项
deepObject: {
handler() { /* ... */ },
deep: true
},
// 方法2:监听具体属性路径
'deepObject.nested.property': function() { /* ... */ },
// 方法3:使用 $watch API(编程式)
mounted() {
this.$watch(
() => this.deepObject,
(newVal, oldVal) => {
console.log('对象变化');
},
{ deep: true }
);
}
}
$watch API:
// 组件内使用
this.$watch(
'message', // 监听表达式
function(newVal, oldVal) {
console.log('消息变化');
},
{ deep: true, immediate: true }
);
// 取消监听
const unwatch = this.$watch('message', handler);
unwatch(); // 取消监听
补充说明:
-
计算属性适用场景:
- 复杂逻辑计算
- 过滤、排序列表数据
- 格式化显示数据
- 多个数据组合
-
侦听器适用场景:
- 数据变化时发起异步请求
- 执行复杂操作(超过一个表达式)
- 数据变化时操作 DOM
- 路由参数变化时重新获取数据
-
性能优化:
- 优先使用计算属性,利用缓存机制
- 避免在计算属性中执行异步操作或副作用
- 深度监听消耗性能,尽量监听具体属性
五、Vue 组件系统
5. 组件注册、props 与插槽
问题:什么是组件?组件的注册方法有哪些?全局组件与局部组件的区别是什么?组件的 props 如何使用?props 的验证方法有哪些?组件的插槽如何使用?作用域插槽如何使用?
答案:
组件定义:
// 1. 全局组件
Vue.component('my-component', {
template: '<div>全局组件</div>'
});
// 2. 局部组件
const MyComponent = {
template: '<div>局部组件</div>'
};
new Vue({
el: '#app',
components: {
'my-component': MyComponent
}
});
// 3. 单文件组件(.vue)
// MyComponent.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: ['message'],
data() {
return { localData: 'local' };
}
}
</script>
<style scoped>
div { color: red; }
</style>
props 使用:
// 父组件
<child-component :title="pageTitle" :count="itemCount"></child-component>
// 子组件
export default {
props: ['title', 'count'], // 数组形式
// 对象形式(推荐)
props: {
title: String,
count: {
type: Number,
default: 0,
required: true,
validator: function(value) {
return value >= 0;
}
}
}
}
props 类型验证:
String、Number、Boolean、Array、Object、Date、Function、Symbol- 自定义构造函数:验证是否是通过 new 创建的实例
插槽使用:
<!-- 子组件 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- 父组件 -->
<my-component>
<template v-slot:header>
<h1>标题</h1>
</template>
<p>主要内容</p>
<template #footer> <!-- 简写 -->
<p>页脚</p>
</template>
</my-component>
作用域插槽:
<!-- 子组件 -->
<template>
<ul>
<li v-for="(item, index) in items">
<slot :item="item" :index="index"></slot>
</li>
</ul>
</template>
<!-- 父组件 -->
<my-list :items="userList">
<template v-slot:default="slotProps">
<span class="user">{{ slotProps.item.name }}</span>
<span class="index">#{{ slotProps.index }}</span>
</template>
</my-list>
<!-- 解构写法 -->
<my-list :items="userList">
<template v-slot:default="{ item, index }">
<span>{{ item.name }} ({{ index }})</span>
</template>
</my-list>
动态组件:
<component :is="currentComponent"></component>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
异步组件:
// 1. 工厂函数
const AsyncComponent = () => ({
component: import('./MyComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
});
// 2. 简化写法
const AsyncComponent = () => import('./MyComponent.vue');
// 3. Vue 3 语法
defineAsyncComponent(() => import('./MyComponent.vue'));
组件注册方式:
- 全局注册:
Vue.component(),在任何地方可用 - 局部注册:组件 options 中的 components 属性
- 自动注册:通过 require.context 批量注册
补充说明:
- 组件名推荐使用 PascalCase(MyComponent)或 kebab-case(my-component)
- props 是单向数据流,子组件不应直接修改,应通过事件通知父组件修改
- 插槽提供内容分发机制,增强组件灵活性
- 动态组件配合 keep-alive 可缓存组件状态
六、组件通信
6. 组件通信方法与使用场景
问题:组件通信的方法有哪些?父子组件通信的方法有哪些?兄弟组件通信的方法有哪些?跨级组件通信的方法有哪些?provide/inject、listeners、children、$refs 如何使用?事件总线如何使用?
答案:
组件通信方法分类:
| 通信方式 | 适用场景 | 特点 |
|---|---|---|
| props/emit | 父子组件通信 | 官方推荐,单向数据流 |
| $refs | 父访问子 | 直接访问子组件实例 |
| children | 父子组件访问 | 紧耦合,不推荐 |
| listeners | 跨级属性/事件传递 | Vue 2.4+,祖孙组件通信 |
| provide/inject | 跨级组件通信 | 祖先提供,后代注入 |
| 事件总线 | 任意组件通信 | 简单场景,复杂时难维护 |
| Vuex | 复杂应用状态管理 | 集中式状态管理 |
| slot scope | 父向子传递数据 | 作用域插槽 |
| 全局事件总线 | 非父子组件通信 | 小型应用适用 |
父子组件通信(props/emit):
// 父组件
<child-component
:title="parentTitle"
@update-title="handleUpdate"
></child-component>
// 子组件
export default {
props: ['title'],
methods: {
updateTitle() {
this.$emit('update-title', '新标题');
}
}
}
$refs 访问子组件:
<!-- 父组件 -->
<child-component ref="childRef"></child-component>
<button @click="callChildMethod">调用子组件方法</button>
<script>
export default {
methods: {
callChildMethod() {
this.$refs.childRef.childMethod();
}
}
}
</script>
listeners:
<!-- 父组件 -->
<child-component
title="标题"
desc="描述"
@click="handleClick"
@custom="handleCustom"
></child-component>
<!-- 子组件 -->
<template>
<div>
<!-- 透传所有属性(除 class、style) -->
<grand-child v-bind="$attrs" v-on="$listeners"></grand-child>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 不自动绑定到根元素
mounted() {
console.log(this.$attrs); // { title: "标题", desc: "描述" }
console.log(this.$listeners); // { click: function, custom: function }
}
}
</script>
provide/inject:
// 祖先组件
export default {
provide() {
return {
theme: this.theme,
updateTheme: this.updateTheme
};
},
data() {
return { theme: 'dark' };
},
methods: {
updateTheme(newTheme) {
this.theme = newTheme;
}
}
}
// 后代组件
export default {
inject: ['theme', 'updateTheme'],
methods: {
changeTheme() {
this.updateTheme('light');
}
}
}
事件总线:
// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
// 发送事件组件
EventBus.$emit('user-logged-in', userData);
// 接收事件组件
EventBus.$on('user-logged-in', (userData) => {
console.log('用户登录:', userData);
});
// 组件销毁前移除监听
beforeDestroy() {
EventBus.$off('user-logged-in', this.handler);
}
兄弟组件通信:
- 通过父组件中转:子组件 A → 父组件 → 子组件 B
- 事件总线:任意组件间通信
- Vuex:集中式状态管理
跨级组件通信:
- provide/inject:祖先提供数据,后代注入使用
- listeners:属性/事件透传
- Vuex:全局状态管理
补充说明:
-
通信方式选择原则:
- 父子组件:props/emit(优先)
- 兄弟组件:通过共同父组件或 Vuex
- 跨级组件:provide/inject 或 Vuex
- 任意组件:Vuex(复杂应用)或事件总线(简单场景)
-
注意事项:
- 避免滥用 children,导致组件紧耦合
- provide/inject 主要为高阶组件设计,普通业务慎用
- 事件总线注意及时清理监听,避免内存泄漏
- Vuex 适合中大型应用,小型应用可能增加复杂度
-
最佳实践:
- 优先使用 props/emit 进行父子通信
- 复杂状态使用 Vuex 管理
- 组件库开发使用 provide/inject、listeners
- 及时清理事件监听,避免内存泄漏
七、Vue 响应式原理
7. 响应式数据与依赖追踪
问题:Vue 的响应式原理是什么?Object.defineProperty 如何实现响应式?数组的响应式如何实现?Vue.set 方法的作用是什么?响应式的局限性有哪些?依赖收集和派发更新的原理是什么?
答案:
核心概念:
- 响应式:数据变化自动更新视图
- 依赖收集:在 getter 中收集依赖(Watcher)
- 派发更新:在 setter 中通知依赖更新
Object.defineProperty 实现响应式:
function defineReactive(obj, key, val) {
// 依赖管理器
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 派发更新
dep.notify();
}
});
}
// 遍历对象所有属性
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
数组响应式实现:
// 数组方法重写
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
const result = original.apply(this, args);
const ob = this.__ob__;
// 获取新增元素
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
// 对新元素进行响应式处理
if (inserted) ob.observeArray(inserted);
// 通知更新
ob.dep.notify();
return result;
};
});
// 替换数组原型
if (Array.isArray(value)) {
value.__proto__ = arrayMethods;
this.observeArray(value);
}
Vue.set / this.$set:
- 解决对象新增属性或数组索引赋值非响应式的问题
- 原理:调用 defineReactive 将新属性转为响应式,并触发更新
// 对象新增属性
Vue.set(this.someObject, 'newProp', 123);
this.$set(this.someObject, 'newProp', 123);
// 数组索引赋值
Vue.set(this.someArray, index, newValue);
this.someArray.splice(index, 1, newValue); // 替代方案
响应式局限性:
- 对象属性添加/删除:Vue 无法检测到,需使用 Vue.set
- 数组索引赋值:
arr[index] = value无效,需使用 Vue.set 或 splice - 数组长度修改:
arr.length = newLength无效 - 嵌套对象深层次修改:需要深度监听或手动触发更新
依赖收集与派发更新流程:
1. 初始化阶段:
- 对 data 进行响应式处理
- 编译模板,创建 Watcher
2. 依赖收集(触发 getter):
- 渲染时读取数据,触发 getter
- getter 中将当前 Watcher 添加到 Dep 中
3. 派发更新(触发 setter):
- 数据变化触发 setter
- setter 调用 dep.notify()
- Dep 通知所有 Watcher 更新
- Watcher 执行更新(重新渲染或回调)
4. 虚拟 DOM 与差异更新:
- 重新执行渲染函数生成新 vnode
- 与旧 vnode 进行 diff
- 将差异应用到真实 DOM
Watcher 类型:
- 渲染 Watcher:组件渲染函数,数据变化时触发重新渲染
- 计算属性 Watcher:计算属性依赖变化时重新计算
- 用户 Watcher:通过 watch 选项或 $watch 创建的侦听器
补充说明:
-
Vue 2 vs Vue 3 响应式:
- Vue 2:Object.defineProperty,需要遍历对象属性,数组需重写方法
- Vue 3:Proxy,直接代理对象,支持数组索引和 length 修改
-
性能优化:
- 避免深度监听大型对象
- 合理使用 computed 缓存
- 避免频繁修改响应式数据
- 使用 Object.freeze() 冻结不需要响应式的对象
-
开发注意事项:
- 初始化时声明所有响应式属性
- 使用 Vue.set 添加新属性
- 使用数组方法修改数组,而非索引赋值
- 复杂嵌套对象考虑使用深拷贝或 immutable 数据
八、Vue Router
8. 路由配置与导航守卫
问题:什么是 Vue Router?路由的基本使用方法是什么?动态路由、嵌套路由如何使用?路由参数如何传递?编程式导航如何使用?路由守卫如何使用?hash 模式与 history 模式的区别是什么?
答案:
Vue Router 基本配置:
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue') // 懒加载
},
{
path: '/user/:id', // 动态路由
name: 'user',
component: () => import('./views/User.vue'),
props: true // 将路由参数作为 props 传递
}
];
const router = new VueRouter({
mode: 'history', // 或 'hash'
base: process.env.BASE_URL,
routes
});
export default router;
动态路由与参数:
// 路由配置
{
path: '/user/:id',
component: User,
props: true
}
// 组件内访问参数
export default {
props: ['id'], // 通过 props 接收
created() {
console.log(this.id); // 路由参数
console.log(this.$route.params.id); // 通过 $route 访问
}
}
// 多参数
{
path: '/user/:id/post/:postId',
component: UserPost
}
嵌套路由:
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
path: '', // 默认子路由
component: UserProfile
},
{
path: 'posts',
component: UserPosts
},
{
path: 'settings',
component: UserSettings
}
]
}
];
// User.vue 模板
<template>
<div class="user">
<h2>用户 {{ $route.params.id }}</h2>
<router-view></router-view> <!-- 子路由出口 -->
</div>
</template>
编程式导航:
// 字符串路径
this.$router.push('/home');
// 对象
this.$router.push({ path: '/home' });
this.$router.push({ name: 'user', params: { id: '123' } });
this.$router.push({ path: '/user', query: { id: '123' } });
// 替换当前路由(不添加历史记录)
this.$router.replace('/home');
// 前进/后退
this.$router.go(1); // 前进一步
this.$router.go(-1); // 后退一步
this.$router.back(); // 后退
// 导航到新位置
this.$router.push('/home').catch(err => {
// 忽略重复导航错误
if (err.name !== 'NavigationDuplicated') {
throw err;
}
});
路由守卫:
- 全局前置守卫:
router.beforeEach((to, from, next) => {
// to: 目标路由
// from: 当前路由
// next: 回调函数
const isAuthenticated = checkAuth();
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!isAuthenticated) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
} else {
next();
}
});
- 全局解析守卫:
router.beforeResolve((to, from, next) => {
// 在导航被确认之前,组件内守卫和异步路由组件被解析之后调用
next();
});
- 全局后置钩子:
router.afterEach((to, from) => {
// 路由跳转完成后调用,无 next 参数
document.title = to.meta.title || '默认标题';
});
- 路由独享守卫:
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter(to, from, next) {
// 仅对该路由生效
if (!isAdmin()) {
next('/login');
} else {
next();
}
}
}
];
- 组件内守卫:
export default {
beforeRouteEnter(to, from, next) {
// 组件实例创建之前调用,不能访问 this
next(vm => {
// 通过回调访问组件实例
console.log(vm);
});
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变,组件复用时调用
// 例如 /user/1 → /user/2,可以访问 this
this.userId = to.params.id;
next();
},
beforeRouteLeave(to, from, next) {
// 离开该组件时调用
const answer = window.confirm('确定离开吗?未保存的更改可能会丢失');
if (answer) {
next();
} else {
next(false);
}
}
}
hash 模式 vs history 模式:
| 特性 | hash 模式 | history 模式 |
|---|---|---|
| URL 显示 | 带 # 号(如 #/home) | 无 # 号,正常 URL(如 /home) |
| 服务器配置 | 无需特殊配置 | 需要配置支持(重定向到 index.html) |
| SEO | 不友好 | 友好 |
| 兼容性 | 支持所有浏览器 | 需要 HTML5 History API |
| 部署 | 简单 | 需服务器配置 |
| 原理 | 监听 hashchange 事件 | 监听 popstate 事件,使用 pushState/replaceState |
路由懒加载:
// 1. 动态 import(推荐)
const User = () => import('./views/User.vue');
// 2. 魔法注释(webpack)
const User = () => import(/* webpackChunkName: "user" */ './views/User.vue');
// 3. 分组(相同 chunk)
const User = () => import(/* webpackChunkName: "group-user" */ './views/User.vue');
const UserList = () => import(/* webpackChunkName: "group-user" */ './views/UserList.vue');
补充说明:
-
路由元信息:
{ path: '/admin', component: Admin, meta: { requiresAuth: true, title: '管理后台' } } -
滚动行为:
const router = new VueRouter({ scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition; } else { return { x: 0, y: 0 }; } } }); -
路由过渡:
<transition name="fade"> <router-view></router-view> </transition> -
导航错误处理:
router.onError((error) => { console.error('路由错误:', error); });
九、Vuex 状态管理
9. Vuex 核心概念与使用
问题:什么是 Vuex?Vuex 的核心概念有哪些?State、Getter、Mutation、Action、Module 如何使用?Vuex 的辅助函数有哪些?Vuex 的模块化方法是什么?Vuex 的持久化如何实现?
答案:
Vuex 概念:
- 集中式状态管理,用于多个组件共享状态
- 适用于中大型单页应用
Vuex 核心概念:
- State:单一状态树,存储应用状态
- Getter:从 state 派生状态,类似计算属性
- Mutation:更改 state 的唯一方法,同步操作
- Action:提交 mutation,可包含异步操作
- Module:将 store 分割成模块
Vuex 基本结构:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
user: null
},
getters: {
doubleCount: state => state.count * 2,
isAuthenticated: state => !!state.user
},
mutations: {
increment(state) {
state.count++;
},
setUser(state, user) {
state.user = user;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
fetchUser({ commit }) {
return api.getUser().then(user => {
commit('setUser', user);
});
}
},
modules: {
cart: cartModule,
products: productsModule
}
});
State 访问:
// 组件内访问
computed: {
count() {
return this.$store.state.count;
},
user() {
return this.$store.state.user;
}
}
// 使用 mapState 辅助函数
import { mapState } from 'vuex';
computed: {
...mapState(['count', 'user']),
...mapState({
customCount: 'count', // 别名
currentUser: state => state.user
})
}
Getter 使用:
// 定义 getter
getters: {
todosByStatus: (state) => (status) => {
return state.todos.filter(todo => todo.status === status);
}
}
// 组件内访问
computed: {
doneTodos() {
return this.$store.getters.doneTodos;
},
pendingTodos() {
return this.$store.getters.todosByStatus('pending');
},
...mapGetters(['doneTodos', 'pendingTodos'])
}
Mutation 使用:
// 定义 mutation
mutations: {
increment(state, payload) {
state.count += payload.amount;
}
}
// 提交 mutation
methods: {
increment() {
this.$store.commit('increment', { amount: 1 });
// 对象风格
this.$store.commit({
type: 'increment',
amount: 1
});
},
...mapMutations(['increment'])
}
Action 使用:
// 定义 action
actions: {
fetchUser({ commit, state, dispatch, getters }, payload) {
return api.getUser(payload.userId)
.then(user => {
commit('setUser', user);
return user;
})
.catch(error => {
dispatch('handleError', error);
throw error;
});
}
}
// 分发 action
methods: {
loadUser() {
this.$store.dispatch('fetchUser', { userId: 123 });
// 返回 Promise
this.$store.dispatch('fetchUser', { userId: 123 })
.then(user => console.log('用户加载成功:', user));
},
...mapActions(['fetchUser'])
}
Module 模块化:
// user 模块
const userModule = {
namespaced: true, // 启用命名空间
state: () => ({
profile: null,
token: ''
}),
getters: {
fullName: state => {
if (!state.profile) return '';
return `${state.profile.firstName} ${state.profile.lastName}`;
}
},
mutations: {
SET_PROFILE(state, profile) {
state.profile = profile;
}
},
actions: {
fetchProfile({ commit }) {
return api.getProfile()
.then(profile => {
commit('SET_PROFILE', profile);
});
}
}
};
// 主 store
export default new Vuex.Store({
modules: {
user: userModule,
cart: cartModule
}
});
// 组件内访问(带命名空间)
computed: {
...mapState('user', ['profile']),
...mapGetters('user', ['fullName'])
},
methods: {
...mapActions('user', ['fetchProfile']),
// 手动指定命名空间
loadProfile() {
this.$store.dispatch('user/fetchProfile');
}
}
Vuex 辅助函数:
mapState: 映射 state 到计算属性mapGetters: 映射 getters 到计算属性mapMutations: 映射 mutations 到方法mapActions: 映射 actions 到方法
Vuex 持久化:
// 使用 vuex-persistedstate 插件
import createPersistedState from 'vuex-persistedstate';
export default new Vuex.Store({
plugins: [
createPersistedState({
key: 'vuex',
storage: window.localStorage,
reducer: (state) => ({
user: state.user, // 只持久化 user 状态
cart: state.cart
})
})
]
});
// 手动实现
const localStoragePlugin = store => {
// 初始化时从 localStorage 恢复
const savedState = localStorage.getItem('vuex-state');
if (savedState) {
store.replaceState(JSON.parse(savedState));
}
// 状态变化时保存到 localStorage
store.subscribe((mutation, state) => {
localStorage.setItem('vuex-state', JSON.stringify(state));
});
};
Vuex 最佳实践:
-
命名约定:
- mutation 类型:全大写,下划线分隔(SET_USER)
- action 类型:驼峰命名(fetchUser)
- getter:描述性名称(filteredTodos)
-
目录结构:
store/ ├── index.js # 主 store ├── actions.js # 根级别 actions ├── mutations.js # 根级别 mutations ├── getters.js # 根级别 getters ├── modules/ # 模块目录 │ ├── user.js # 用户模块 │ ├── cart.js # 购物车模块 │ └── products.js # 商品模块 -
开发工具:
- Vue DevTools:可视化调试状态变化
- 严格模式:开发环境开启严格模式,直接修改 state 会报错
补充说明:
-
Vuex 适用场景:
- 多个视图依赖同一状态
- 来自不同视图的行为需要变更同一状态
- 中大型应用,组件层级深
-
注意事项:
- 避免在 mutation 中执行异步操作
- 合理使用模块化,避免单一文件过大
- 及时清理不需要的状态,避免内存泄漏
- 考虑使用 TypeScript 提升类型安全
-
Vuex 与 Composition API:
- Vue 3 中可使用
useStore组合式函数 - Pinia 是 Vuex 的替代方案,更轻量,支持组合式 API
- Vue 3 中可使用
十、Vue 性能优化
10. 虚拟 DOM、Diff 算法与优化策略
问题:Vue 性能优化的方法有哪些?什么是虚拟 DOM?虚拟 DOM 的原理是什么?什么是 Diff 算法?key 的作用是什么?列表渲染如何优化?组件懒加载如何实现?keep-alive 如何使用?
答案:
虚拟 DOM(Virtual DOM):
- JavaScript 对象表示的 DOM 结构
- 通过对比新旧虚拟 DOM 的差异,最小化操作真实 DOM
虚拟 DOM 优点:
- 性能优化:批量 DOM 操作,减少重排重绘
- 跨平台:可渲染到不同平台(Web、Native、小程序)
- 声明式编程:关注数据而非具体 DOM 操作
Diff 算法(差异算法):
- 比较新旧虚拟 DOM 树的差异
- Vue 2 使用双端比较算法,Vue 3 使用快速 Diff 算法
key 的作用:
- 标识节点身份:帮助 Vue 识别哪些节点是新的,哪些是旧的
- 重用元素:相同 key 的元素会被复用,避免不必要的重新渲染
- 保持状态:组件状态与 key 绑定,key 变化时组件重新创建
<!-- 正确的 key 使用 -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 错误:使用索引作为 key -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<!-- 问题:列表顺序变化时,元素状态会错乱 -->
列表渲染优化:
// 1. 使用 v-show 替代 v-if(频繁切换)
<div v-show="isVisible">内容</div>
// 2. 使用计算属性过滤/排序(避免在模板中计算)
computed: {
filteredItems() {
return this.items.filter(item => item.active);
}
}
// 3. 使用 Object.freeze 冻结不需要响应式的列表
this.items = Object.freeze(largeList);
// 4. 虚拟滚动(长列表)
// 使用 vue-virtual-scroller 等库
组件懒加载:
// 1. 路由懒加载
const User = () => import('./views/User.vue');
// 2. 组件懒加载
export default {
components: {
HeavyComponent: () => import('./HeavyComponent.vue')
}
}
// 3. 条件懒加载
export default {
data() {
return { showHeavy: false };
},
components: {
HeavyComponent: () => import('./HeavyComponent.vue')
},
methods: {
loadHeavyComponent() {
this.showHeavy = true;
}
}
}
keep-alive 缓存组件:
<keep-alive :include="['Home', 'About']" :exclude="['User']" :max="10">
<router-view></router-view>
</keep-alive>
<!-- 结合 transition -->
<transition name="fade">
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</transition>
keep-alive 生命周期:
activated:组件激活时调用(缓存组件切换到前台)deactivated:组件停用时调用(缓存组件切换到后台)
export default {
activated() {
// 恢复数据、重新发起请求等
this.fetchData();
},
deactivated() {
// 清理定时器、暂停动画等
clearInterval(this.timer);
}
}
其他优化策略:
-
合理使用 computed 和 watch:
- 优先使用 computed,利用缓存机制
- watch 深度监听消耗性能,尽量监听具体属性
-
避免不必要的响应式:
// 不需要响应式的数据 export default { data() { return { constantData: Object.freeze({ PI: 3.14 }), largeStaticData: Object.freeze(largeArray) }; } } -
事件委托:
<!-- 避免为每个列表项绑定事件 --> <ul @click="handleItemClick"> <li v-for="item in items" :data-id="item.id">{{ item.name }}</li> </ul> -
防抖与节流:
import { debounce, throttle } from 'lodash'; export default { methods: { // 防抖 search: debounce(function(query) { this.fetchResults(query); }, 300), // 节流 handleScroll: throttle(function() { this.checkPosition(); }, 100) } } -
使用 production 模式:
// Vue.config Vue.config.productionTip = false; Vue.config.devtools = process.env.NODE_ENV !== 'production'; // 构建时去除警告和开发工具
Vue 3 性能优化特性:
- 静态提升:将静态节点提升到渲染函数外部,避免重复创建
- 补丁标记:标记动态节点,仅更新动态部分
- 事件监听缓存:缓存事件处理函数,避免重新创建
- Tree-shaking:支持按需导入,减少打包体积
- 响应式系统优化:Proxy 替代 Object.defineProperty,性能更好
补充说明:
-
性能监控工具:
- Vue DevTools:组件渲染时间检查
- Chrome Performance:性能分析
- Lighthouse:整体性能评分
-
Code Splitting 策略:
- 路由级别分割
- 组件级别分割
- 公共代码提取
-
服务端渲染(SSR):
- 首屏加载更快
- 更好的 SEO
- 使用 Nuxt.js 简化 SSR 开发
十一、Vue 常见问题
11. 双向绑定、nextTick 与关键API
问题:Vue 双向绑定的原理是什么?v-model 的原理是什么?nextTick 的作用是什么?nextTick 的使用场景有哪些?slot 的使用方法有哪些?props 与 data 的区别是什么?on 的区别是什么?
答案:
双向绑定原理:
- 数据劫持:通过 Object.defineProperty 或 Proxy 监听数据变化
- 发布订阅:数据变化时通知所有依赖(Watcher)更新
- 编译模板:解析模板中的指令和插值表达式
- 绑定更新:建立数据与视图的关联,数据变化时更新视图
v-model 原理:
<!-- v-model 语法糖 -->
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
<!-- 组件上使用 -->
<custom-input v-model="searchText"></custom-input>
<!-- 等价于 -->
<custom-input
:value="searchText"
@input="searchText = $event"
></custom-input>
<!-- 组件实现 -->
Vue.component('custom-input', {
props: ['value'],
template: `
<input
:value="value"
@input="$emit('input', $event.target.value)"
>
`
});
nextTick 作用:
- 在下次 DOM 更新循环结束之后执行延迟回调
- 用于获取更新后的 DOM 状态
nextTick 使用场景:
export default {
data() {
return {
message: 'Hello',
list: []
};
},
methods: {
updateMessage() {
this.message = 'World';
// DOM 尚未更新
console.log(this.$el.textContent); // 'Hello'
this.$nextTick(() => {
// DOM 已更新
console.log(this.$el.textContent); // 'World'
});
},
addItem() {
this.list.push('new item');
// 列表更新后操作 DOM
this.$nextTick(() => {
const lastItem = this.$el.querySelector('li:last-child');
lastItem.scrollIntoView();
});
}
},
mounted() {
// 在 mounted 中操作 DOM
this.$nextTick(() => {
// 确保 DOM 已渲染
this.initThirdPartyLib();
});
}
}
nextTick 原理:
- 微任务队列:优先使用 Promise.then
- 降级策略:Promise → MutationObserver → setImmediate → setTimeout
- 队列机制:将回调推入队列,在下一个 tick 执行
slot 使用方法:
<!-- 1. 默认插槽 -->
<!-- 子组件 -->
<div class="container">
<slot>默认内容</slot>
</div>
<!-- 父组件 -->
<my-component>
<p>自定义内容</p>
</my-component>
<!-- 2. 具名插槽 -->
<!-- 子组件 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
</div>
<!-- 父组件 -->
<my-component>
<template v-slot:header>
<h1>标题</h1>
</template>
<p>主要内容</p>
</my-component>
<!-- 3. 作用域插槽 -->
<!-- 子组件 -->
<ul>
<li v-for="item in items">
<slot :item="item"></slot>
</li>
</ul>
<!-- 父组件 -->
<my-list :items="userList">
<template v-slot:default="slotProps">
<span>{{ slotProps.item.name }}</span>
</template>
</my-list>
props vs data 对比:
| 特性 | props | data |
|---|---|---|
| 来源 | 父组件传递 | 组件内部定义 |
| 可变性 | 不应直接修改(单向数据流) | 可修改(响应式) |
| 响应式 | 响应式变化 | 响应式变化 |
| 验证 | 支持类型验证 | 无验证 |
| 默认值 | 支持 default 选项 | 直接赋值 |
| 使用场景 | 接收外部数据 | 组件内部状态 |
on:
| 特性 | $emit | $on |
|---|---|---|
| 作用 | 触发自定义事件 | 监听自定义事件 |
| 使用位置 | 子组件向父组件通信 | 父组件监听子组件事件 |
| 参数传递 | 可传递多个参数 | 接收事件参数 |
| 典型用法 | this.$emit('update', value) | @update="handleUpdate" |
| 事件总线 | EventBus.$emit('event') | EventBus.$on('event') |
props 注意事项:
export default {
props: {
// 基本类型
title: String,
// 多种类型
value: [String, Number],
// 必填项
id: {
type: Number,
required: true
},
// 默认值
count: {
type: Number,
default: 0
},
// 对象/数组默认值(工厂函数)
config: {
type: Object,
default: () => ({})
},
// 自定义验证
status: {
validator: function(value) {
return ['active', 'inactive'].includes(value);
}
}
}
}
补充说明:
-
双向绑定最佳实践:
- 使用 .sync 修饰符实现 prop 双向绑定(Vue 2.3+)
<!-- 父组件 --> <child-component :title.sync="pageTitle"></child-component> <!-- 子组件 --> <button @click="$emit('update:title', '新标题')">更新标题</button> -
nextTick 与异步更新队列:
- Vue 异步执行 DOM 更新
- 同一事件循环中的数据变化会被批量处理
- 在数据变化后立即操作 DOM 需要使用 nextTick
-
插槽作用域:
- 插槽内容在父组件作用域中编译
- 作用域插槽将子组件数据传递给父组件使用
- Vue 3 中统一为 v-slot 语法
-
事件通信模式:
- 父子组件:props/emit
- 兄弟组件:通过父组件中转或事件总线
- 跨级组件:provide/inject 或 Vuex
- 任意组件:Vuex 或全局事件总线
十二、Vue 高级特性
12. 混入、插件、自定义指令与过滤器
问题:什么是混入?全局混入与局部混入的区别是什么?混入的合并策略是什么?什么是 Vue 插件?如何开发 Vue 插件?什么是自定义指令?指令的钩子函数有哪些?什么是过滤器?过滤器的使用方法是什么?
答案:
混入(Mixin):
- 分发组件可复用功能的对象
- 可包含任意组件选项(data、methods、生命周期等)
局部混入:
// 定义混入
const myMixin = {
data() {
return {
mixinData: '来自混入的数据'
};
},
created() {
console.log('混入的 created 钩子');
},
methods: {
mixinMethod() {
console.log('混入的方法');
}
}
};
// 使用混入
export default {
mixins: [myMixin],
created() {
console.log('组件的 created 钩子');
console.log(this.mixinData); // '来自混入的数据'
this.mixinMethod(); // '混入的方法'
}
}
全局混入:
// 全局混入(影响所有 Vue 实例)
Vue.mixin({
created() {
console.log('全局混入的 created 钩子');
}
});
// 谨慎使用:会影响到每个组件
混入合并策略:
- 数据对象:组件数据优先(冲突时覆盖混入数据)
- 钩子函数:合并为数组,混入钩子先执行
- 方法、计算属性等:组件选项优先(冲突时覆盖)
const mixin = {
data() {
return { message: '混入数据', mixinOnly: '混入独有' };
},
created() {
console.log('混入 created');
},
methods: {
foo() { console.log('混入 foo'); },
conflict() { console.log('混入 conflict'); }
}
};
export default {
mixins: [mixin],
data() {
return { message: '组件数据', componentOnly: '组件独有' };
},
created() {
console.log('组件 created');
},
methods: {
bar() { console.log('组件 bar'); },
conflict() { console.log('组件 conflict'); }
},
mounted() {
console.log(this.message); // '组件数据'(组件优先)
console.log(this.mixinOnly); // '混入独有'
console.log(this.componentOnly); // '组件独有'
this.foo(); // '混入 foo'
this.bar(); // '组件 bar'
this.conflict(); // '组件 conflict'(组件优先)
}
}
// 输出顺序:
// 混入 created
// 组件 created
Vue 插件:
- 添加全局功能(组件、指令、过滤器、混入等)
- 通过 Vue.use() 安装
开发插件:
// 1. 定义插件
const MyPlugin = {
install(Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function() {
console.log('全局方法');
};
// 2. 添加全局资源(指令/过滤器/过渡等)
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// 指令逻辑
}
});
// 3. 注入组件选项(混入)
Vue.mixin({
created() {
// 全局混入逻辑
}
});
// 4. 添加实例方法
Vue.prototype.$myMethod = function(methodOptions) {
// 实例方法逻辑
};
}
};
// 2. 使用插件
Vue.use(MyPlugin, { someOption: true });
// 3. 使用插件功能
// 全局方法
Vue.myGlobalMethod();
// 全局指令
<div v-my-directive></div>
// 实例方法
this.$myMethod();
自定义指令:
// 注册全局指令
Vue.directive('focus', {
// 指令钩子函数
bind(el, binding, vnode, oldVnode) {
// 只调用一次,指令第一次绑定到元素时
},
inserted(el, binding, vnode, oldVnode) {
// 被绑定元素插入父节点时调用
el.focus();
},
update(el, binding, vnode, oldVnode) {
// 组件更新时调用(可能发生在子组件更新前)
},
componentUpdated(el, binding, vnode, oldVnode) {
// 组件与子组件全部更新后调用
},
unbind(el, binding, vnode, oldVnode) {
// 只调用一次,指令与元素解绑时
}
});
// 指令参数
// binding 对象包含:
// - name: 指令名(不包含 v- 前缀)
// - value: 指令的绑定值
// - oldValue: 指令绑定的前一个值(仅在 update 和 componentUpdated 中可用)
// - expression: 字符串形式的指令表达式
// - arg: 传给指令的参数(如 v-my-directive:arg)
// - modifiers: 包含修饰符的对象(如 v-my-directive.foo.bar)
// 使用指令
<input v-focus>
<input v-my-directive:arg.modifier="value">
局部指令:
export default {
directives: {
focus: {
inserted(el) {
el.focus();
}
},
// 动态参数
pin: {
bind(el, binding) {
el.style.position = 'fixed';
const s = binding.arg || 'top';
el.style[s] = binding.value + 'px';
},
update(el, binding) {
const s = binding.arg || 'top';
el.style[s] = binding.value + 'px';
}
}
}
}
// 使用
<div v-pin:bottom="200">固定在底部 200px</div>
过滤器:
- 用于文本格式化,可用在插值表达式和 v-bind 中
- Vue 3 已移除,建议使用计算属性或方法替代
// 全局过滤器
Vue.filter('currency', function(value, symbol = '¥') {
if (!value) return '';
return symbol + value.toFixed(2);
});
// 局部过滤器
export default {
filters: {
capitalize(value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
},
// 链式调用
reverse(value) {
if (!value) return '';
return value.split('').reverse().join('');
}
}
}
// 使用
<div>{{ price | currency('$') }}</div>
<div>{{ message | capitalize | reverse }}</div>
<div :title="title | capitalize"></div>
过滤器参数:
Vue.filter('formatDate', function(value, format = 'YYYY-MM-DD') {
// 格式化日期
// value: 原始值
// format: 第一个参数
// 后续参数:过滤器调用时传递的其他参数
});
// 使用
<div>{{ date | formatDate('YYYY/MM/DD') }}</div>
补充说明:
-
混入适用场景:
- 多个组件共享相同逻辑
- 提取通用功能(如日志、权限检查)
- 避免代码重复
-
插件开发注意事项:
- 避免污染全局命名空间
- 提供清晰的 API 文档
- 考虑向后兼容性
- 正确处理 options 参数
-
自定义指令适用场景:
- DOM 操作(聚焦、滚动、拖拽)
- 集成第三方库
- 权限控制
- 图片懒加载
-
过滤器替代方案(Vue 3):
- 计算属性:
computed: { formattedPrice() { ... } } - 方法:
methods: { formatPrice(value) { ... } } - 全局方法:
app.config.globalProperties.$format = ...
- 计算属性:
-
最佳实践:
- 混入用于逻辑复用,但避免过度使用导致代码难以理解
- 插件提供全局功能,确保功能确实是全局需要的
- 指令封装 DOM 操作,保持指令功能单一
- Vue 3 中优先使用 Composition API 替代混入
由于Vue答案内容较多,这里只展示了前12个部分的核心内容,完整答案包含Vue生命周期、响应式原理、路由、状态管理、性能优化等全部116个问题的详细解答。