MST

星途 面试题库

面试题:JavaScript事件循环机制在复杂异步场景下的优化策略

假设你正在开发一个具有大量异步操作(如频繁的网络请求、DOM更新等)的Web应用,结合JavaScript事件循环机制,说明可以采取哪些优化策略来提升性能,避免出现卡顿现象。
49.8万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试
  1. 合理使用微任务与宏任务
    • 宏任务调度:宏任务(如setTimeoutsetInterval、DOM事件回调等)会在每个事件循环周期的宏任务队列中按顺序执行。避免在宏任务回调中执行过多同步且耗时的操作,防止阻塞后续宏任务的执行。例如,如果有复杂的数据处理,可以将其拆分到多个宏任务中。比如在一个处理大量数据的函数中,不要一次性处理完所有数据,可以使用setTimeout将数据分块处理,每次处理一部分数据。
    let largeDataArray = [/* 大量数据 */];
    let index = 0;
    function processDataChunk() {
        let chunkSize = 100;
        for (let i = 0; i < chunkSize && index < largeDataArray.length; i++) {
            // 处理数据
            let data = largeDataArray[index];
            // 数据处理逻辑
            index++;
        }
        if (index < largeDataArray.length) {
            setTimeout(processDataChunk, 0);
        }
    }
    setTimeout(processDataChunk, 0);
    
    • 微任务使用:微任务(如Promise.thenMutationObserver等)会在当前宏任务执行结束后,下一个宏任务开始前执行。避免在微任务队列中添加过多任务,以免长时间占用事件循环,导致页面卡顿。例如,在使用Promise链式调用时,尽量减少不必要的.then回调,确保微任务队列的简短。
  2. 优化网络请求
    • 合并请求:如果有多个相关的网络请求,可以考虑合并为一个请求。例如,在获取用户信息及其相关配置时,如果原本需要分别发送两个请求,可以通过后端接口优化,使一次请求就能获取到所有数据,减少网络请求次数,从而减少异步操作的数量,提升性能。
    • 控制并发请求数量:过多的并发网络请求会占用大量资源,导致性能下降。可以使用Promise.allSettledPromise.race等方法结合队列控制并发请求数量。比如,使用一个队列来存储待发送的请求,每次从队列中取出一定数量(如3个)的请求并发执行,当前一批请求完成后,再从队列中取出下一批请求。
    function sendRequests(requests, maxConcurrent) {
        let results = [];
        let queue = requests.slice();
        let running = 0;
        function processNext() {
            while (running < maxConcurrent && queue.length > 0) {
                running++;
                let request = queue.shift();
                request().then(result => {
                    results.push(result);
                    running--;
                    if (queue.length > 0) {
                        processNext();
                    }
                });
            }
        }
        processNext();
        return Promise.allSettled(results);
    }
    
  3. DOM更新优化
    • 批量更新:避免频繁的DOM更新,因为每次DOM更新都会触发重排和重绘,消耗性能。可以将要更新的DOM操作集中起来,一次性执行。例如,使用DocumentFragment来批量操作DOM。先将所有的DOM元素创建或修改操作在DocumentFragment上进行,然后将DocumentFragment添加到页面DOM树中,这样只会触发一次重排和重绘。
    let fragment = document.createDocumentFragment();
    // 对fragment进行多次DOM元素创建、修改等操作
    let newDiv = document.createElement('div');
    newDiv.textContent = '新的div';
    fragment.appendChild(newDiv);
    // 将fragment添加到页面DOM树
    document.body.appendChild(fragment);
    
    • 防抖与节流:对于一些会频繁触发的DOM事件(如scrollresize等),使用防抖(debounce)或节流(throttle)技术。防抖是指在事件触发后,等待一定时间(如300毫秒),如果在此期间事件再次触发,则重新计时,直到等待时间结束才执行回调函数。节流是指在一定时间间隔内(如200毫秒),无论事件触发多少次,都只执行一次回调函数。
    // 防抖函数
    function debounce(func, delay) {
        let timer;
        return function() {
            let context = this;
            let args = arguments;
            clearTimeout(timer);
            timer = setTimeout(() => {
                func.apply(context, args);
            }, delay);
        };
    }
    // 节流函数
    function throttle(func, interval) {
        let lastTime = 0;
        return function() {
            let context = this;
            let args = arguments;
            let now = new Date().getTime();
            if (now - lastTime >= interval) {
                func.apply(context, args);
                lastTime = now;
            }
        };
    }
    window.addEventListener('scroll', debounce(() => {
        // 处理滚动事件的逻辑
    }, 300));
    window.addEventListener('resize', throttle(() => {
        // 处理窗口大小改变事件的逻辑
    }, 200));