MST

星途 面试题库

面试题:Vue应用中如何优化异步数据请求以提升性能及用户体验

从网络层面(如缓存策略、请求合并)、组件层面(如数据预取、按需加载)以及代码层面(如异步请求并发控制)等多个角度,详细阐述在大型Vue应用中优化异步数据请求的策略和方法,并举例说明如何结合具体业务场景实施这些优化措施。
15.6万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

网络层面

  1. 缓存策略
    • 强缓存:在Vue应用中,可以利用HTTP缓存头(如Cache - ControlExpires)来设置强缓存。例如,对于一些不经常变化的静态数据接口(如网站配置信息),可以设置较长的缓存时间。
    // 在后端设置缓存头(以Node.js + Express为例)
    const express = require('express');
    const app = express();
    app.get('/config', (req, res) => {
        res.set('Cache - Control','public, max - age = 3600');// 缓存1小时
        res.send({/* 配置信息 */});
    });
    
    • 协商缓存:通过Last - ModifiedETag头来实现。当数据可能有变化但变化频率不高时适用。在Vue应用的created钩子函数中发送请求时,带上If - Modified - Since(对应Last - Modified)或If - None - Match(对应ETag)头。
    // 在Vue组件中
    export default {
        created() {
            const headers = {};
            const lastModified = localStorage.getItem('configLastModified');
            if (lastModified) {
                headers['If - Modified - Since'] = lastModified;
            }
            this.$http.get('/config', { headers }).then(response => {
                if (response.status === 200) {
                    localStorage.setItem('configLastModified', response.headers['last - modified']);
                    // 处理数据
                } else if (response.status === 304) {
                    // 从缓存中读取数据
                }
            });
        }
    };
    
  2. 请求合并
    • 防抖:对于频繁触发的异步请求(如搜索框输入实时搜索),可以使用防抖策略。在Vue中可以通过自定义指令或在方法中使用setTimeout实现。
    <template>
        <input v - model="searchText" @input="debouncedSearch">
    </template>
    <script>
    export default {
        data() {
            return {
                searchText: '',
                timer: null
            };
        },
        methods: {
            debouncedSearch() {
                clearTimeout(this.timer);
                this.timer = setTimeout(() => {
                    this.$http.get('/search', { params: { q: this.searchText } }).then(response => {
                        // 处理搜索结果
                    });
                }, 300);
            }
        }
    };
    </script>
    
    • 节流:适用于固定频率触发的请求场景,如滚动加载更多。通过控制请求的频率,避免短时间内多次请求相同数据。
    <template>
        <div @scroll="throttledLoadMore">
            <!-- 页面内容 -->
        </div>
    </template>
    <script>
    export default {
        data() {
            return {
                throttleTimer: null,
                page: 1
            };
        },
        methods: {
            throttledLoadMore() {
                if (!this.throttleTimer) {
                    this.throttleTimer = setTimeout(() => {
                        this.$http.get('/loadMore', { params: { page: this.page } }).then(response => {
                            // 处理加载更多的数据
                            this.page++;
                            this.throttleTimer = null;
                        });
                    }, 200);
                }
            }
        }
    };
    </script>
    

组件层面

  1. 数据预取
    • 在路由导航守卫中预取:在Vue Router的beforeEach守卫中,根据即将进入的路由,提前获取相关数据。例如,在一个博客应用中,当用户点击文章列表中的某篇文章链接时,在进入文章详情页之前,提前获取文章内容。
    import router from './router';
    import store from './store';
    router.beforeEach((to, from, next) => {
        if (to.name === 'ArticleDetail') {
            const articleId = to.params.id;
            store.dispatch('fetchArticle', articleId).then(() => {
                next();
            });
        } else {
            next();
        }
    });
    
    • 在组件生命周期钩子中预取:在组件的createdbeforeMount钩子函数中提前获取数据。比如一个商品详情组件,在组件创建时就获取商品的详细信息。
    export default {
        created() {
            const productId = this.$route.params.productId;
            this.$http.get(`/products/${productId}`).then(response => {
                this.product = response.data;
            });
        }
    };
    
  2. 按需加载
    • 动态导入组件:对于一些不常用的组件(如用户设置中的高级设置组件),可以使用动态导入。在Vue中通过import()语法实现。
    const routes = [
        {
            path: '/settings/advanced',
            name: 'AdvancedSettings',
            component: () => import('./components/AdvancedSettings.vue')
        }
    ];
    
    • 图片懒加载:在Vue应用中,可以使用vue - lazyload插件来实现图片的懒加载。这在展示大量图片的页面(如商品列表页)中很有用。
    <template>
        <div v - for="product in products" :key="product.id">
            <img v - lazy="product.imageUrl" alt="Product Image">
            <p>{{ product.name }}</p>
        </div>
    </template>
    <script>
    import VueLazyload from 'vue - lazyload';
    export default {
        data() {
            return {
                products: []
            };
        },
        created() {
            this.$http.get('/products').then(response => {
                this.products = response.data;
            });
        }
    };
    Vue.use(VueLazyload);
    </script>
    

代码层面

  1. 异步请求并发控制
    • 使用Promise.all进行并发请求并控制数量:在需要同时获取多个数据但又要控制并发数量的场景下,例如一个电商应用中同时获取商品信息、库存信息和评论数量。假设我们限制并发数为3。
    export default {
        data() {
            return {
                productInfo: null,
                stockInfo: null,
                reviewCount: null
            };
        },
        created() {
            const requests = [
                this.$http.get('/productInfo'),
                this.$http.get('/stockInfo'),
                this.$http.get('/reviewCount'),
                this.$http.get('/otherInfo1'),
                this.$http.get('/otherInfo2')
            ];
            const maxConcurrent = 3;
            const results = [];
            for (let i = 0; i < requests.length; i += maxConcurrent) {
                const batch = requests.slice(i, i + maxConcurrent);
                Promise.all(batch).then(values => {
                    results.push(...values);
                    if (results.length === requests.length) {
                        this.productInfo = results[0].data;
                        this.stockInfo = results[1].data;
                        this.reviewCount = results[2].data;
                        // 处理其他结果
                    }
                });
            }
        }
    };
    
    • 使用async/await配合队列实现并发控制:可以自己实现一个简单的队列来控制请求并发。
    class RequestQueue {
        constructor(maxConcurrent) {
            this.maxConcurrent = maxConcurrent;
            this.queue = [];
            this.running = 0;
        }
        enqueue(request) {
            return new Promise((resolve, reject) => {
                this.queue.push({ request, resolve, reject });
                this.runNext();
            });
        }
        runNext() {
            while (this.running < this.maxConcurrent && this.queue.length > 0) {
                const { request, resolve, reject } = this.queue.shift();
                this.running++;
                request().then(response => {
                    resolve(response);
                    this.running--;
                    this.runNext();
                }).catch(error => {
                    reject(error);
                    this.running--;
                    this.runNext();
                });
            }
        }
    }
    const queue = new RequestQueue(3);
    export default {
        data() {
            return {
                productInfo: null,
                stockInfo: null,
                reviewCount: null
            };
        },
        async created() {
            const [productResponse, stockResponse, reviewResponse] = await Promise.all([
                queue.enqueue(() => this.$http.get('/productInfo')),
                queue.enqueue(() => this.$http.get('/stockInfo')),
                queue.enqueue(() => this.$http.get('/reviewCount'))
            ]);
            this.productInfo = productResponse.data;
            this.stockInfo = stockResponse.data;
            this.reviewCount = reviewResponse.data;
        }
    };