面试题答案
一键面试- 合理使用微任务与宏任务
- 宏任务调度:宏任务(如
setTimeout
、setInterval
、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.then
、MutationObserver
等)会在当前宏任务执行结束后,下一个宏任务开始前执行。避免在微任务队列中添加过多任务,以免长时间占用事件循环,导致页面卡顿。例如,在使用Promise
链式调用时,尽量减少不必要的.then
回调,确保微任务队列的简短。
- 宏任务调度:宏任务(如
- 优化网络请求
- 合并请求:如果有多个相关的网络请求,可以考虑合并为一个请求。例如,在获取用户信息及其相关配置时,如果原本需要分别发送两个请求,可以通过后端接口优化,使一次请求就能获取到所有数据,减少网络请求次数,从而减少异步操作的数量,提升性能。
- 控制并发请求数量:过多的并发网络请求会占用大量资源,导致性能下降。可以使用
Promise.allSettled
或Promise.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); }
- 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事件(如
scroll
、resize
等),使用防抖(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));
- 批量更新:避免频繁的DOM更新,因为每次DOM更新都会触发重排和重绘,消耗性能。可以将要更新的DOM操作集中起来,一次性执行。例如,使用