H5 JavaScript 事件循环与任务调度

举报
yd_290101916 发表于 2026/09/24 09:05:10 2026/09/24
【摘要】 H5 JavaScript 事件循环与任务调度JavaScript 在浏览器主线程上执行时,需要与用户输入、样式计算、布局和绘制共享时间。异步代码并不自动代表不会阻塞:Promise 回调过多、长循环和连续同步 DOM 操作都可能让页面迟迟无法渲染。理解任务、微任务和帧调度,有助于安排真正合适的执行时机。 一、调用栈必须先清空同步代码进入调用栈并依次执行。只要当前函数没有返回,浏览器就不能...

H5 JavaScript 事件循环与任务调度

JavaScript 在浏览器主线程上执行时,需要与用户输入、样式计算、布局和绘制共享时间。异步代码并不自动代表不会阻塞:Promise 回调过多、长循环和连续同步 DOM 操作都可能让页面迟迟无法渲染。理解任务、微任务和帧调度,有助于安排真正合适的执行时机。

一、调用栈必须先清空

同步代码进入调用栈并依次执行。只要当前函数没有返回,浏览器就不能处理下一项主线程任务。

function calculateTotal(items: CartItem[]): number {
  let total = 0;
  for (const item of items) {
    total += item.priceInCent * item.quantity;
  }
  return total;
}

少量数据没有问题,但数十万条复杂计算会长时间占用主线程。是否拆分应由真实耗时决定。

二、任务与微任务的顺序

计时器、用户事件等通常进入任务队列;Promise 回调进入微任务队列。当前任务结束后,浏览器会清空微任务,再获得渲染机会。

console.log('同步开始');

setTimeout(() => {
  console.log('定时任务');
}, 0);

Promise.resolve().then(() => {
  console.log('微任务');
});

console.log('同步结束');

输出顺序是同步开始、同步结束、微任务、定时任务。零延迟计时器表示尽快排队,不表示立即执行。

三、微任务也可能阻塞渲染

持续创建新的微任务,会让微任务队列长期无法清空,浏览器没有机会绘制。

function scheduleMicrotaskLoop(): void {
  Promise.resolve().then(scheduleMicrotaskLoop);
}

这类代码即使没有传统同步死循环,也会让页面失去响应。需要让出渲染机会时,应切换到新的任务或按帧继续,而不是只使用 Promise 链。

四、按批次拆分长任务

不要求一次同步完成的数据处理可以拆成小批次:

async function processInBatches<T>(
  items: readonly T[],
  batchSize: number,
  handle: (item: T) => void
): Promise<void> {
  for (let start = 0; start < items.length; start += batchSize) {
    const end = Math.min(start + batchSize, items.length);
    for (let index = start; index < end; index += 1) {
      handle(items[index]);
    }

    await new Promise<void>(resolve => setTimeout(resolve, 0));
  }
}

拆分会增加总调度成本,批次大小需要测量。金额提交等必须原子完成的业务逻辑不能随意拆开。

五、视觉更新使用帧回调

动画和滚动联动应在下一绘制帧统一更新:

let scheduled = false;
let latestOffset = 0;

function onScroll(offset: number): void {
  latestOffset = offset;
  if (scheduled) return;
  scheduled = true;

  requestAnimationFrame(() => {
    header.style.transform = `translateY(${-latestOffset}px)`;
    scheduled = false;
  });
}

一帧内多次滚动事件只产生一次 DOM 写入,并使用最新值。

六、计时器不能承担精确计时

页面进入后台、设备繁忙或节电策略生效时,计时器会被延迟。倒计时不应每次简单减一,而应根据目标时刻和当前时刻重新计算剩余时间。

function remainingSeconds(deadline: number, now: number): number {
  return Math.max(0, Math.ceil((deadline - now) / 1000));
}

关键业务时间以服务端权威时间和规则为准,前端计时只负责展示。

七、计算密集任务考虑工作线程

大型解析、图像处理和复杂统计可以放入工作线程,但线程通信需要复制或转移数据,也有启动成本。

只有任务足够重、与 DOM 无关且输入输出边界清晰时才值得迁移。工作线程不能直接操作页面元素,结果仍需回到主线程渲染。

八、避免同步等待

主线程同步等待资源或锁会冻结整个页面。异步接口应以 Promise 或事件返回结果,让浏览器在等待期间处理其他任务。

多个异步任务之间有共同成败或先后依赖时,应在代码结构中明确,而不是通过固定延迟猜测完成顺序。

九、页面生命周期影响调度

页面进入后台后,动画帧会暂停或降频,计时器也会节流。回到前台时,应根据最新业务状态重新计算界面,而不是补执行所有错过的动画帧。

页面销毁时取消仍在等待的任务、计时器和监听,防止旧回调更新新页面。

十、用时间线验证

性能分析中可以查看:

  • 单个任务持续时间。
  • 微任务数量是否异常。
  • 渲染帧是否被长任务跨越。
  • 输入事件到视觉反馈的间隔。
  • 批处理后总耗时与交互响应是否改善。

总结

事件循环决定了异步工作与渲染如何共享主线程。同步栈结束后会先清空微任务,过多微任务同样会阻塞绘制。计算任务按业务允许的批次让出执行权,视觉更新按帧合并,并用真实时间而非计时器次数计算状态,页面才能保持响应。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。