vue前端面试题——记录一次面试当中遇到的题(5)
目录
1.请求响应慢,怎么处理?
一、诊断分析阶段
二、前端层面优化
三、网络层面优化
四、服务端协作优化
五、用户体验优化
六、监控与告警
实际项目案例
面试回答要点总结
2.怎么防止用户重复点击?
一、UI层面防护(最直观的用户反馈)
二、请求层面拦截(核心防护)
三、业务层面防护(精细化控制)
四、高级防护方案
五、实际项目中的综合方案
面试回答要点总结
1.请求响应慢,怎么处理?
请求响应慢是一个综合性的性能问题,我会从前端、网络、服务端三个层面进行系统化分析和优化。首先需要通过监控工具定位瓶颈,然后针对性地实施优化措施。
一、诊断分析阶段
1. 性能监控与问题定位
使用浏览器开发者工具分析
- Network 面板:查看请求耗时、排队时间、TTFB
- Performance 面板:分析主线程活动和长任务
- Lighthouse:获取性能评分和建议
// 1. 使用浏览器开发者工具分析
// - Network 面板:查看请求耗时、排队时间、TTFB
// - Performance 面板:分析主线程活动和长任务
// - Lighthouse:获取性能评分和建议// 2. 自定义性能监控
const requestStartTime = Date.now();fetch('/api/data').then(response => {const timings = {total: Date.now() - requestStartTime,dns: 0, // 可通过 Performance API 获取详细时序tcp: 0,ttfb: 0};// 上报性能数据this.reportPerformance(timings);});// 3. 关键性能指标
const performanceMetrics = {TTFB: 'Time to First Byte', // 首字节时间FCP: 'First Contentful Paint', // 首次内容绘制LCP: 'Largest Contentful Paint' // 最大内容绘制
};
2. 问题分类诊断
// 判断问题类型
function diagnoseSlowRequest(metrics) {if (metrics.TTFB > 1000) {return '服务端处理慢或网络延迟高';}if (metrics.downloadTime > 3000) {return '响应数据过大或网络带宽低';}if (metrics.queueingTime > 500) {return '浏览器请求队列阻塞';}return '需要进一步分析';
}
二、前端层面优化
1. 请求合并与减少
// 1.1 请求合并
class RequestBatcher {constructor() {this.batch = new Map();this.timer = null;}addRequest(key, request) {this.batch.set(key, request);if (!this.timer) {this.timer = setTimeout(() => this.flush(), 50);}}async flush() {const requests = Array.from(this.batch.values());// 合并多个请求为一个批量请求const batchResponse = await fetch('/api/batch', {method: 'POST',body: JSON.stringify({ requests })});this.batch.clear();this.timer = null;}
}// 1.2 避免重复请求
const requestCache = new Map();async function cachedRequest(url, options) {const cacheKey = JSON.stringify({ url, options });if (requestCache.has(cacheKey)) {return requestCache.get(cacheKey);}const promise = fetch(url, options).then(response => {// 缓存成功响应if (response.ok) {requestCache.set(cacheKey, response.clone());}return response;});requestCache.set(cacheKey, promise);return promise;
}
2. 请求优化策略
// 2.1 请求优先级管理
class RequestScheduler {constructor(maxConcurrent = 6) {this.maxConcurrent = maxConcurrent;this.activeCount = 0;this.queue = [];}async schedule(request, priority = 'normal') {return new Promise((resolve, reject) => {const task = { request, resolve, reject, priority };// 按优先级插入队列this.insertByPriority(task);this.processQueue();});}insertByPriority(task) {const priorities = { high: 0, normal: 1, low: 2 };const taskPriority = priorities[task.priority];let index = this.queue.findIndex(item => priorities[item.priority] > taskPriority);if (index === -1) index = this.queue.length;this.queue.splice(index, 0, task);}async processQueue() {if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) {return;}this.activeCount++;const task = this.queue.shift();try {const result = await task.request();task.resolve(result);} catch (error) {task.reject(error);} finally {this.activeCount--;this.processQueue();}}
}// 2.2 使用 Web Workers 处理复杂计算
// main.js
const worker = new Worker('request-worker.js');worker.postMessage({type: 'API_REQUEST',payload: { url: '/api/complex-data' }
});worker.onmessage = (event) => {if (event.data.type === 'RESPONSE') {this.processData(event.data.payload);}
};// request-worker.js
self.onmessage = async (event) => {if (event.data.type === 'API_REQUEST') {const response = await fetch(event.data.payload.url);const data = await response.json();// 在 Worker 中进行数据处理,不阻塞主线程const processedData = processDataInWorker(data);self.postMessage({type: 'RESPONSE',payload: processedData});}
};
3. 缓存策略优化
// 3.1 多级缓存策略
class CacheManager {constructor() {this.memoryCache = new Map();this.localStorageKey = 'api-cache';}async getWithCache(url, options = {}) {const { ttl = 300000 } = options; // 默认5分钟// 1. 内存缓存(最快)const memoryKey = this.generateKey(url, options);if (this.memoryCache.has(memoryKey)) {const cached = this.memoryCache.get(memoryKey);if (Date.now() - cached.timestamp < ttl) {return cached.data;}}// 2. localStorage 缓存const storageCache = this.getStorageCache();const storageKey = this.generateKey(url, options);if (storageCache[storageKey] && Date.now() - storageCache[storageKey].timestamp < ttl) {// 回填内存缓存this.memoryCache.set(memoryKey, storageCache[storageKey]);return storageCache[storageKey].data;}// 3. 网络请求try {const response = await fetch(url, options);const data = await response.json();const cacheItem = {data,timestamp: Date.now()};// 更新缓存this.memoryCache.set(memoryKey, cacheItem);this.updateStorageCache(storageKey, cacheItem);return data;} catch (error) {// 返回过期的缓存数据(如果有)if (storageCache[storageKey]) {return storageCache[storageKey].data;}throw error;}}generateKey(url, options) {return btoa(JSON.stringify({ url, options }));}
}
三、网络层面优化
1. HTTP/2 与连接优化
// 1.1 HTTP/2 多路复用
// 服务器配置 HTTP/2,前端无需特殊处理
// 但可以优化资源加载顺序// 1.2 DNS 预解析
const dnsPrefetchLinks = ['//api.example.com','//cdn.example.com'
];dnsPrefetchLinks.forEach(domain => {const link = document.createElement('link');link.rel = 'dns-prefetch';link.href = domain;document.head.appendChild(link);
});// 1.3 预连接
const preconnectLinks = ['https://api.example.com'
];preconnectLinks.forEach(domain => {const link = document.createElement('link');link.rel = 'preconnect';link.href = domain;document.head.appendChild(link);
});
2. 数据压缩与传输优化
// 2.1 请求数据压缩
async function sendCompressedRequest(url, data) {// 压缩请求体const compressedData = pako.gzip(JSON.stringify(data));const response = await fetch(url, {method: 'POST',headers: {'Content-Type': 'application/json','Content-Encoding': 'gzip'},body: compressedData});return response;
}// 2.2 响应数据流式处理
async function streamLargeResponse(url) {const response = await fetch(url);const reader = response.body.getReader();const decoder = new TextDecoder();while (true) {const { done, value } = await reader.read();if (done) break;// 分批处理数据,避免阻塞const chunk = decoder.decode(value, { stream: true });this.processChunk(chunk);}
}// 2.3 数据分页与懒加载
class PaginatedLoader {constructor(pageSize = 20) {this.pageSize = pageSize;this.currentPage = 0;this.hasMore = true;}async loadNextPage() {if (!this.hasMore) return;const response = await fetch(`/api/data?page=${this.currentPage}&size=${this.pageSize}`);const data = await response.json();this.currentPage++;this.hasMore = data.hasMore;return data.items;}
}
四、服务端协作优化
1. API 设计优化建议
// 1.1 GraphQL 精确获取数据
const query = `query GetUserData($id: ID!) {user(id: $id) {nameemailposts(limit: 5) {titlecreatedAt}}}
`;// 1.2 批量接口设计
const batchRequest = {requests: [{ path: '/users/1', method: 'GET' },{ path: '/posts/recent', method: 'GET' },{ path: '/notifications', method: 'GET' }]
};// 1.3 增量更新接口
const incrementalUpdate = {since: '2024-01-01T00:00:00Z',types: ['users', 'posts', 'comments']
};
2. 缓存头优化
// 建议服务端设置合适的缓存头
const optimalCacheHeaders = {// 静态资源:长期缓存'Cache-Control': 'public, max-age=31536000, immutable',// 动态数据:短期缓存'Cache-Control': 'private, max-age=300', // 5分钟// 实时数据:不缓存'Cache-Control': 'no-cache, no-store'
};// 前端检查缓存状态
function checkCacheStatus(response) {const cacheHeader = response.headers.get('Cache-Control');if (cacheHeader && cacheHeader.includes('max-age')) {const maxAge = parseInt(cacheHeader.match(/max-age=(\d+)/)[1]);console.log(`数据缓存时间: ${maxAge}秒`);}
}
五、用户体验优化
1. 加载状态管理
<template><div><!-- 骨架屏 --><div v-if="loading" class="skeleton-container"><div class="skeleton-item" v-for="n in 5" :key="n"></div></div><!-- 实际内容 --><div v-else><div v-for="item in data" :key="item.id" class="data-item">{{ item.name }}</div></div><!-- 加载更多 --><button v-if="hasMore && !loading" @click="loadMore":disabled="loadingMore">{{ loadingMore ? '加载中...' : '加载更多' }}</button></div>
</template><script>
export default {data() {return {loading: false,loadingMore: false,data: [],hasMore: true}},methods: {async loadData() {this.loading = true;try {// 设置超时const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), 10000));const dataPromise = this.fetchData();this.data = await Promise.race([dataPromise, timeoutPromise]);} catch (error) {this.handleError(error);} finally {this.loading = false;}},handleError(error) {if (error.message === '请求超时') {this.showToast('请求超时,请重试');} else {this.showToast('加载失败');}// 重试逻辑if (this.retryCount < 3) {setTimeout(() => this.loadData(), 2000);this.retryCount++;}}}
}
</script>
2. 智能重试与降级
// 2.1 指数退避重试
class RetryableRequest {constructor(maxRetries = 3, baseDelay = 1000) {this.maxRetries = maxRetries;this.baseDelay = baseDelay;}async execute(requestFn) {let lastError;for (let attempt = 0; attempt <= this.maxRetries; attempt++) {try {return await requestFn();} catch (error) {lastError = error;// 非重试错误if (error.status && [400, 401, 403, 404].includes(error.status)) {throw error;}if (attempt === this.maxRetries) break;// 指数退避const delay = this.baseDelay * Math.pow(2, attempt);await this.sleep(delay + Math.random() * 1000);}}throw lastError;}sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));}
}// 2.2 服务降级策略
class FallbackStrategy {constructor() {this.fallbacks = new Map();}register(service, fallbackFn) {this.fallbacks.set(service, fallbackFn);}async executeWithFallback(service, mainFn) {try {return await mainFn();} catch (error) {console.warn(`服务 ${service} 失败,使用降级方案`);const fallbackFn = this.fallbacks.get(service);if (fallbackFn) {return await fallbackFn();}throw error;}}
}// 使用示例
const fallback = new FallbackStrategy();// 注册降级方案
fallback.register('user-api', async () => {// 返回本地缓存或默认数据return localStorage.getItem('cached-users') || [];
});// 执行请求
const userData = await fallback.executeWithFallback('user-api',() => fetch('/api/users')
);
六、监控与告警
1. 性能数据收集
// 性能监控SDK
class PerformanceMonitor {constructor() {this.metrics = [];this.reportUrl = '/api/performance';}recordRequest(url, startTime, endTime, success) {const duration = endTime - startTime;this.metrics.push({url,duration,success,timestamp: new Date().toISOString()});// 批量上报if (this.metrics.length >= 10) {this.report();}}async report() {if (this.metrics.length === 0) return;try {await fetch(this.reportUrl, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ metrics: this.metrics })});this.metrics = [];} catch (error) {console.warn('性能数据上报失败:', error);}}// 慢请求告警checkSlowRequests() {const slowThreshold = 3000; // 3秒this.metrics.forEach(metric => {if (metric.duration > slowThreshold) {this.alertSlowRequest(metric);}});}
}
实际项目案例
"在我负责的电商项目中,我们遇到了商品列表API响应慢的问题:
问题分析:
-
TTFB平均2.8秒,95分位值达到5秒
-
并发请求过多导致浏览器队列阻塞
-
服务端数据库查询未优化
优化措施:
// 1. 前端请求优化
const optimizedStrategy = {// 请求合并batchRequests: true,// 数据分页pagination: { pageSize: 20 },// 缓存策略cache: { ttl: 60000 },// 优先级调度priority: { visible: 'high', background: 'low' }
};// 2. 与服务端协作
const apiOptimizations = {// 添加查询索引addedIndexes: ['category', 'price', 'created_at'],// 引入Redis缓存cacheLayer: 'redis',// 数据库读写分离readReplicas: true
};
面试回答要点总结
"总结请求响应慢的优化方案:
-
诊断先行:使用专业工具定位性能瓶颈
-
前端优化:请求合并、缓存、优先级调度、Web Workers
-
网络优化:HTTP/2、预连接、数据压缩、CDN
-
服务端协作:API设计优化、缓存策略、数据库优化
-
用户体验:骨架屏、智能重试、服务降级
-
监控告警:性能数据收集、慢请求监控
2.怎么防止用户重复点击?
防止用户重复点击是一个常见的用户体验优化需求,我会根据不同的业务场景采用分层级的解决方案,主要从UI层防抖、请求层拦截、业务层防护三个层面来设计
一、UI层面防护(最直观的用户反馈)
1. 按钮状态控制
<template><button :class="['submit-btn', { 'loading': loading, 'disabled': disabled }]":disabled="loading || disabled"@click="handleSubmit"><span v-if="loading"><i class="loading-spinner"></i>提交中...</span><span v-else>{{ buttonText }}</span></button>
</template><script>
export default {data() {return {loading: false,disabled: false}},methods: {async handleSubmit() {if (this.loading) returnthis.loading = truetry {await this.submitForm()// 提交成功后可以暂时禁用按钮,防止连续提交this.disabled = truesetTimeout(() => {this.disabled = false}, 2000) // 2秒后恢复} catch (error) {console.error('提交失败:', error)} finally {this.loading = false}},async submitForm() {// 模拟API请求return new Promise(resolve => {setTimeout(resolve, 1000)})}}
}
</script><style scoped>
.submit-btn {padding: 12px 24px;border: none;border-radius: 6px;background: #1890ff;color: white;cursor: pointer;transition: all 0.3s;
}.submit-btn:hover:not(.disabled) {background: #40a9ff;
}.submit-btn.loading {background: #91d5ff;cursor: not-allowed;
}.submit-btn.disabled {background: #d9d9d9;cursor: not-allowed;
}.loading-spinner {display: inline-block;width: 12px;height: 12px;border: 2px solid transparent;border-top: 2px solid white;border-radius: 50%;animation: spin 1s linear infinite;margin-right: 8px;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}
</style>
2. 防抖函数封装
// utils/debounce.js
export function debounce(func, wait, immediate = false) {let timeout, resultconst debounced = function(...args) {const context = thisif (timeout) clearTimeout(timeout)if (immediate) {// 立即执行版本const callNow = !timeouttimeout = setTimeout(() => {timeout = null}, wait)if (callNow) result = func.apply(context, args)} else {// 延迟执行版本timeout = setTimeout(() => {func.apply(context, args)}, wait)}return result}debounced.cancel = function() {clearTimeout(timeout)timeout = null}return debounced
}// 在组件中使用
import { debounce } from '@/utils/debounce'export default {methods: {// 防抖处理,500ms内只能点击一次handleClick: debounce(function() {this.submitData()}, 500),// 立即执行版本,先执行然后冷却handleInstantClick: debounce(function() {this.submitData()}, 1000, true)}
}
二、请求层面拦截(核心防护)
1. 请求锁机制
// utils/request-lock.js
class RequestLock {constructor() {this.pendingRequests = new Map()}// 生成请求唯一标识generateKey(config) {const { method, url, data, params } = configreturn JSON.stringify({ method, url, data, params })}// 添加请求到等待队列add(config) {const key = this.generateKey(config)if (this.pendingRequests.has(key)) {return false // 请求已存在,拒绝重复请求}this.pendingRequests.set(key, true)return true}// 移除请求remove(config) {const key = this.generateKey(config)this.pendingRequests.delete(key)}// 清空所有请求clear() {this.pendingRequests.clear()}
}export const requestLock = new RequestLock()// 在请求拦截器中使用
import axios from 'axios'
import { requestLock } from '@/utils/request-lock'// 请求拦截器
axios.interceptors.request.use(config => {// 检查是否是重复请求if (!requestLock.add(config)) {// 如果是重复请求,取消本次请求return Promise.reject(new Error('重复请求已被取消'))}return config
})// 响应拦截器
axios.interceptors.response.use(response => {// 请求完成,从等待队列移除requestLock.remove(response.config)return response},error => {// 请求失败,也从等待队列移除if (error.config) {requestLock.remove(error.config)}return Promise.reject(error)}
)
2. 基于 Promise 的请求锁
// utils/promise-lock.js
class PromiseLock {constructor() {this.locks = new Map()}async acquire(key) {// 如果已经有相同的请求在进行,返回它的 Promiseif (this.locks.has(key)) {return this.locks.get(key)}// 创建新的 Promise 并存储let resolveLockconst lockPromise = new Promise(resolve => {resolveLock = resolve})this.locks.set(key, lockPromise)// 返回一个释放锁的函数return () => {resolveLock()this.locks.delete(key)}}// 直接执行带锁的函数async execute(key, asyncFunction) {const release = await this.acquire(key)try {const result = await asyncFunction()return result} finally {release()}}
}export const promiseLock = new PromiseLock()// 在业务代码中使用
import { promiseLock } from '@/utils/promise-lock'export default {methods: {async submitOrder() {try {const result = await promiseLock.execute('submit-order', // 锁的key() => this.api.submitOrder(this.orderData))this.$message.success('下单成功')return result} catch (error) {if (error.message.includes('锁已存在')) {this.$message.warning('请求正在处理中,请勿重复点击')} else {this.$message.error('下单失败')}throw error}}}
}
三、业务层面防护(精细化控制)
1. 页面级防护
<template><div class="page-container"><!-- 全局加载遮罩 --><div v-if="pageLoading" class="global-loading"><div class="loading-content"><i class="loading-icon"></i><p>处理中,请稍候...</p></div></div><form @submit.prevent="handleGlobalSubmit"><!-- 表单内容 --><button type="submit">提交</button></form></div>
</template><script>
export default {data() {return {pageLoading: false,lastSubmitTime: 0,submitCooldown: 3000 // 3秒冷却}},methods: {async handleGlobalSubmit() {const now = Date.now()// 检查冷却时间if (now - this.lastSubmitTime < this.submitCooldown) {this.$message.warning(`操作过于频繁,请${Math.ceil((this.submitCooldown - (now - this.lastSubmitTime)) / 1000)}秒后再试`)return}this.pageLoading = truethis.lastSubmitTime = nowtry {await this.submitAllData()this.$message.success('提交成功')} catch (error) {console.error('提交失败:', error)this.lastSubmitTime = 0 // 失败时重置冷却时间} finally {this.pageLoading = false}}}
}
</script>
2. 路由级防护
// utils/navigation-guard.js
import { requestLock } from './request-lock'let isNavigating = falseexport const navigationGuard = {install(Vue, options) {// 路由跳转前检查Vue.mixin({beforeRouteLeave(to, from, next) {// 如果有请求正在进行,提示用户if (requestLock.pendingRequests.size > 0) {if (confirm('当前有请求正在处理,确定要离开吗?')) {requestLock.clear() // 清空所有请求next()} else {next(false)}} else {next()}}})// 防止重复导航const originalPush = VueRouter.prototype.pushVueRouter.prototype.push = function push(location) {if (isNavigating) {return Promise.reject(new Error('导航进行中'))}isNavigating = truereturn originalPush.call(this, location).finally(() => {isNavigating = false})}}
}// 在main.js中使用
import { navigationGuard } from '@/utils/navigation-guard'
Vue.use(navigationGuard)
四、高级防护方案
1. 指令式防护
// directives/click-once.js
export const clickOnce = {bind(el, binding, vnode) {let executed = falselet loading = falseconst handler = async function(event) {if (executed || loading) {event.preventDefault()event.stopPropagation()return}const { value } = bindingif (typeof value === 'function') {loading = trueel.classList.add('click-once-loading')try {await value.call(vnode.context, event)executed = trueel.classList.add('click-once-executed')} catch (error) {console.error('点击执行失败:', error)} finally {loading = falseel.classList.remove('click-once-loading')}}}el._clickOnceHandler = handlerel.addEventListener('click', handler)},unbind(el) {if (el._clickOnceHandler) {el.removeEventListener('click', el._clickOnceHandler)}}
}// 全局注册
import Vue from 'vue'
import { clickOnce } from '@/directives/click-once'
Vue.directive('click-once', clickOnce)// 在模板中使用
<template><button v-click-once="handlePayment">支付</button>
</template>
2. 组合式API防护(Vue3)
// composables/usePreventDuplicateClick.js
import { ref, onUnmounted } from 'vue'export function usePreventDuplicateClick(options = {}) {const {cooldown = 1000,onDuplicate,onCooldown} = optionsconst isSubmitting = ref(false)const lastSubmitTime = ref(0)const timers = []const execute = async (asyncFunction) => {const now = Date.now()// 检查冷却时间if (now - lastSubmitTime.value < cooldown) {onCooldown?.(Math.ceil((cooldown - (now - lastSubmitTime.value)) / 1000))return}// 检查是否正在提交if (isSubmitting.value) {onDuplicate?.()return}isSubmitting.value = truelastSubmitTime.value = nowtry {const result = await asyncFunction()return result} finally {isSubmitting.value = false}}const withLoading = async (asyncFunction) => {return execute(asyncFunction)}// 清理定时器onUnmounted(() => {timers.forEach(timer => clearTimeout(timer))})return {isSubmitting,execute,withLoading}
}// 在Vue3组件中使用
<script setup>
import { usePreventDuplicateClick } from '@/composables/usePreventDuplicateClick'const { isSubmitting, execute } = usePreventDuplicateClick({cooldown: 2000,onDuplicate: () => {console.log('请勿重复点击')},onCooldown: (seconds) => {console.log(`请${seconds}秒后再试`)}
})const handleSubmit = async () => {const result = await execute(() => api.submit(data))if (result) {console.log('提交成功')}
}
</script><template><button :disabled="isSubmitting" @click="handleSubmit">{{ isSubmitting ? '提交中...' : '提交' }}</button>
</template>
五、实际项目中的综合方案
1. 完整的防护体系
// utils/submission-manager.js
class SubmissionManager {constructor() {this.submissions = new Map()this.defaultCooldown = 3000}// 开始提交start(key, cooldown = this.defaultCooldown) {const now = Date.now()const submission = this.submissions.get(key)if (submission) {const timeSinceLast = now - submission.lastSubmitif (timeSinceLast < cooldown) {return {allowed: false,waitTime: cooldown - timeSinceLast}}}this.submissions.set(key, {lastSubmit: now,cooldown,inProgress: true})return { allowed: true }}// 结束提交finish(key, success = true) {const submission = this.submissions.get(key)if (submission) {submission.inProgress = falseif (!success) {// 失败时重置,允许立即重试submission.lastSubmit = 0}}}// 检查是否允许提交canSubmit(key) {const submission = this.submissions.get(key)if (!submission) return trueconst now = Date.now()return !submission.inProgress && (now - submission.lastSubmit >= submission.cooldown)}// 获取等待时间getWaitTime(key) {const submission = this.submissions.get(key)if (!submission) return 0const now = Date.now()return Math.max(0, submission.cooldown - (now - submission.lastSubmit))}
}export const submissionManager = new SubmissionManager()// 在业务组件中使用
export default {methods: {async submitForm() {const formKey = `form-${this.formId}`const checkResult = submissionManager.start(formKey)if (!checkResult.allowed) {this.$message.warning(`请${Math.ceil(checkResult.waitTime / 1000)}秒后再试`)return}try {const result = await this.api.submit(this.formData)submissionManager.finish(formKey, true)this.$message.success('提交成功')return result} catch (error) {submissionManager.finish(formKey, false)this.$message.error('提交失败')throw error}}}
}
面试回答要点总结
"总结防止用户重复点击的解决方案:
分层防护策略:
-
UI层面:
-
按钮loading状态
-
视觉反馈和禁用状态
-
防抖函数控制点击频率
-
-
请求层面:
-
请求锁机制,拦截重复请求
-
Promise锁,保证同一操作只执行一次
-
请求队列管理
-
-
业务层面:
-
页面级加载状态
-
路由导航防护
-
操作冷却时间控制
-
-
高级方案:
-
自定义指令封装
-
组合式API(Vue3)
-
完整的提交管理器
-
技术选型建议:
-
简单场景:按钮loading状态 + 防抖
-
中等复杂度:请求拦截器 + Promise锁
-
复杂系统:完整的提交管理 + 指令封装
在实际项目中,我通常会根据业务重要性采用组合策略,比如关键支付操作使用多重防护,普通表单使用基础防护。"