解锁浏览器空闲时间:requestIdleCallback

requestIdleCallback 是一个 Web API,它允许开发者在浏览器空闲时执行某些任务。这个 API 设计目的是为了让开发者能够在浏览器渲染帧之间,或其他非关键时刻执行一些低优先级的工作,从而避免阻塞主线程,影响用户的交互体验。

1. 来源和背景

requestIdleCallback 是由 Chrome 首先引入的,并且已经被其他浏览器(如 Firefox、Safari)部分支持。它是在 Web Performance API 的一部分,并且专门为 “空闲时间” 提供的 API。

空闲时间通常是指,浏览器没有被用户交互(如点击、滚动等)或更新渲染(如页面动画、DOM更新等)占用的时间。

与 setTimeout 和 setInterval 等传统的定时器 API 不同,浏览器将 requestIdleCallback 调度为一个任务队列中的回调函数,且该回调函数将在浏览器空闲时执行,这样可以最大化渲染的流畅度,避免占用主线程资源。

1.1 浏览器的事件循环

在了解 requestIdleCallback 之前,先理解浏览器的事件循环机制

事件循环是浏览器处理 JavaScript 代码、用户输入、渲染和其他任务的核心机制。它的基本流程如下:

  • 执行栈:当 JavaScript 代码被执行时,它会被推入执行栈。执行栈是一个后进先出(LIFO)的数据结构。
  • 任务队列:当异步操作(如网络请求、定时器等)完成时,相关的回调会被放入任务队列。任务队列是一个先进先出(FIFO)的数据结构。
  • 渲染队列:浏览器会在适当的时候进行页面渲染。渲染通常发生在 JavaScript 执行完成后。
  • 空闲时间:当执行栈为空且没有待处理的任务时,浏览器会进入空闲状态。
1.2 空闲时间计算
1. 检测空闲状态
  • 执行栈为空:浏览器会检查执行栈是否为空。如果没有正在执行的 JavaScript 代码,浏览器认为可以进入空闲状态。
  • 任务队列为空:浏览器还会检查任务队列是否为空。如果没有待处理的任务,浏览器就会认为当前处于空闲状态。
2. 计算空闲时间
  • 时间片:浏览器通常为每个帧分配一个时间片,通常是 16 毫秒(对应 60 FPS 的帧率)。在这个时间片内,如果没有任务需要执行,浏览器就会认为有空闲时间。
  • 剩余时间:当 requestIdleCallback 的回调被调用时,开发者可以使用 deadline.timeRemaining() 方法来获取当前空闲时间的剩余毫秒数。这个方法返回一个数字,表示在下一个帧渲染之前的剩余时间。

下面先简单提一下具体流程。

3. 处理流程

当调用 requestIdleCallback 时,流程如下:

  1. 调用 requestIdleCallback:传入一个回调函数,浏览器将其排入待执行的队列。
    1. requestIdleCallback(myCallback);
  2. 空闲检查
    1. 浏览器在每一帧中检查执行栈和任务队列的状态。如果执行栈为空且任务队列也为空,浏览器会认为当前处于空闲状态。
  3. 执行回调
    1. 如果当前没有任务需要处理,浏览器会调用 requestIdleCallback 中的回调函数。
    2. 在回调中,使用 deadline.timeRemaining() 来判断当前的空闲时间。
    3. function myCallback(deadline) {
        while (deadline.timeRemaining() > 0) {
            // 执行一些非紧急的任务
            console.log('执行任务...');
        }
        // 如果还有任务未完成,可以在下次空闲时继续处理
        if (/* 还有更多任务 */) {
            requestIdleCallback(myCallback);
        }
      }
      
  4.  超时处理
    1. 如果设置了超时参数,浏览器会在超时到达时强制调用回调,即使没有空闲时间。
    2. requestIdleCallback(myCallback, { timeout: 1000 });
      

2. 基本用法

requestIdleCallback 用法与传统的 setTimeout 类似,但它有一些特殊的处理机制。最简单的使用方式如下:

<body>
  <button id="startButton">启动空闲任务</button>
  <div id="output"></div>

  <script>
    const startButton = document.getElementById('startButton');
    const output = document.getElementById('output');

    // 定义一个模拟的低优先级任务
    function lowPriorityTask(deadline) {
      // deadline.timeRemaining() 返回当前空闲时间还剩下多少毫秒
      while (deadline.timeRemaining() > 0) {
        // 模拟一些耗时操作
        const newElement = document.createElement('p');
        newElement.textContent = `随机数生成: ${Math.random()}`;
        output.appendChild(newElement);

        // 如果已经生成了 100 个随机数,停止任务
        if (output.children.length >= 100) {
          return;
        }
      }

      // 如果空闲时间不足,继续请求下一个空闲回调
      requestIdleCallback(lowPriorityTask);
    }

    // 给按钮添加点击事件监听器
    startButton.addEventListener('click', () => {
      // 开始请求空闲回调
      requestIdleCallback(lowPriorityTask);
    });
  </script>
</body>

参数说明:

  • deadline:该对象包含有关剩余空闲时间的信息,主要有两个属性:
    •  timeRemaining():返回当前空闲周期中剩余的时间,单位是毫秒。
    • didTimeout:布尔值,指示回调是否因为超时而被执行。

Tip:requestIdleCallback 不会强制执行任务,它会尽量在浏览器空闲时执行回调,但如果浏览器忙于其他任务,回调可能会延迟执行。

3. 引入原因

浏览器的渲染引擎通常会占用主线程来处理用户交互和渲染工作,这导致开发者有时难以在这些繁忙时刻执行一些额外的任务(例如后台数据加载、日志记录、非关键的 DOM 操作等)。

requestIdleCallback 主要解决问题包括:

  • 避免阻塞主线程:通过让低优先级任务在空闲时执行,避免影响页面渲染或用户交互。
  • 提高性能:允许开发者在用户不操作时做一些后台处理,避免强制中断用户体验。
3.1 优缺点

1、优点

  • 非阻塞性:任务会在浏览器空闲时执行,因此不会中断用户的交互和页面渲染。
  • 灵活性:通过 timeRemaining() 函数,开发者可以判断当前空闲时间是否足够,灵活安排任务。
  • 性能优化:有助于处理一些低优先级的任务(如懒加载、数据同步、日志记录等),从而避免主线程长时间被占用,提高用户体验。

2、缺点

  • 浏览器支持有限:虽然在 Chrome 中已被支持,但其他浏览器(如 Safari 和 Firefox)并不全面支持。如果存在兼容多个浏览器问题,就需要额外处理。
  • 无法控制任务的精确执行时机:回调函数的执行是由浏览器调度的,不能精确控制。任务可能会延迟执行,甚至在极端情况下根本不执行(例如,如果页面一直繁忙)。
  • 不保证立即执行:与 setTimeout 和 setInterval 不同,requestIdleCallback 并不保证立即执行回调函数。它是根据空闲时间来安排执行的。

4. 对比:requestIdleCallback vs setTimeout vs setInterval

特性requestIdleCallbacksetTimeoutsetInterval
执行时机

在浏览器空闲时执行(优先级低的任务)

在指定的时间后执行,可能会在高优先级任务期间被中断在指定时间间隔周期性执行,可能会在高优先级任务期间被中断
适用场景空闲时间的低优先级任务(如懒加载、后台同步、日志记录等)延迟执行某个任务,通常用于定时操作(如动画、轮询)定时周期性执行任务(如轮询、计时器)
优先级低优先级任务,且不会阻塞页面渲染或用户交互可以与浏览器渲染任务争夺主线程资源,影响性能与 setTimeout 类似,可能影响页面渲染性能
支持性目前主要在 Chrome 支持,其他浏览器支持较差广泛支持(几乎所有浏览器)广泛支持(几乎所有浏览器)
可控性不能精确控制执行时机,只能在空闲时执行可以精确控制执行时机可以精确控制执行时机,周期性执行任务

5. 实际 🌰

1. 延迟加载图片

典型例子:假设有一个网页,里面有很多图片,希望用户滚动到这些图片时再加载它们。可以使用 requestIdleCallback 来在浏览器空闲时加载这些图片。

const images = document.querySelectorAll('img[data-src]');

function loadImages() {
  images.forEach((img) => {
    if (img.getBoundingClientRect().top < window.innerHeight) {
      img.src = img.dataset.src; // 将 data-src 的值赋给 src
      img.removeAttribute('data-src'); // 移除 data-src 属性
    }
  });
}

function idleCallback(deadline) {
  while (deadline.timeRemaining() > 0 && images.length > 0) {
    loadImages();
  }
}

// 每次空闲时调用
requestIdleCallback(idleCallback);
2. 分块处理任务

假设有一个需要处理大量数据的任务,比如渲染一个长列表。可以将这个任务分成多个小块,在浏览器空闲时逐步处理。

const data = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
let index = 0;

function renderItems(deadline) {
  while (deadline.timeRemaining() > 0 && index < data.length) {
    const item = document.createElement('div');
    item.textContent = data[index++];
    document.body.appendChild(item);
  }

  if (index < data.length) {
    requestIdleCallback(renderItems); // 继续处理剩余的项
  }
}

// 开始处理
requestIdleCallback(renderItems);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值