面试题答案
一键面试async/await
与 JavaScript 微任务队列机制的关系
async/await
基于 Promise:async
函数始终返回一个 Promise 对象。当async
函数内部遇到await
关键字时,它会暂停函数的执行,直到其等待的 Promise 被解决(resolved)或被拒绝(rejected)。- 微任务队列:JavaScript 有一个事件循环机制,其中微任务队列是事件循环的一部分。Promise 的
then
回调会被放入微任务队列。当当前执行栈清空后,事件循环会优先处理微任务队列中的任务,然后再处理宏任务队列。 async/await
与微任务队列的关联:当await
一个已解决的 Promise 时,await
之后的代码会被包装成一个微任务并放入微任务队列。例如:
async function asyncFunction() {
const result = await Promise.resolve(1);
console.log(result); // 会在当前执行栈清空后,作为微任务执行
}
asyncFunction();
通过优化微任务队列使用提升 async/await
性能的方法
- 控制微任务数量:避免在短时间内产生过多的微任务。例如,不要在一个循环内同步创建大量
await
操作,因为每次await
后的代码都会作为微任务排队。可以使用Promise.all
来并行处理多个异步任务,将多个await
合并为一个:
// 不好的写法,短时间内产生大量微任务
for (let i = 0; i < 1000; i++) {
const result = await someAsyncFunction(i);
// 处理 result
}
// 好的写法,使用 Promise.all
const promises = Array.from({ length: 1000 }, (_, i) => someAsyncFunction(i));
const results = await Promise.all(promises);
// 处理 results
- 合理安排微任务顺序:将对性能影响较小的微任务放在后面执行。例如,如果有一些更新 UI 的微任务和一些数据计算的微任务,优先执行数据计算微任务,避免 UI 更新的频繁阻塞。
实际项目中性能问题场景及优化方案
- 场景:在一个实时数据更新的 Web 应用中,需要频繁从服务器获取数据并更新 UI。假设使用
async/await
来处理每个数据获取请求:
async function updateData() {
while (true) {
const data = await fetchData();
updateUI(data);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
这里每次获取数据和更新 UI 操作都会产生微任务。如果 fetchData
操作频繁且响应较快,会导致微任务队列堆积,可能造成 UI 卡顿。
2. 优化方案:
- 节流与防抖:可以对数据获取操作进行节流或防抖处理。例如,使用防抖函数,确保在一定时间内只有一次数据获取请求,避免短时间内过多的微任务产生:
function debounce(func, delay) {
let timer;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
const debouncedFetchData = debounce(fetchData, 500);
async function updateData() {
while (true) {
const data = await debouncedFetchData();
updateUI(data);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
- **合并请求**:如果可能,将多个数据获取请求合并为一个。例如,服务器支持批量获取数据,就可以一次性获取多个数据块,减少请求次数,从而减少微任务数量。