可以被取代的鸡肋-requestIdleCallback

发布时间:2024年01月11日

在这里插入图片描述

文章概叙

本文主要写的是window对象上的requestIdleCallback,用一个例子示范下如何使用requestIdleCallback。

浏览器空闲时期

在理解requestIdleCallback的概念之前,我们需要知道一个概念,叫做浏览器的空闲时期。

众所周知,浏览器是会不断地去绘制页面,比如我们最常听到的版本是每隔16.7ms的时候刷新一次页面,以保证流畅性。

一开始的时候,我们的网页刚进来,由于任务很繁重,所以会出现页面卡顿,或者是白屏,如下图的performance中显示的花花绿绿的方块。
在这里插入图片描述

而当我们的页面渲染的比较"稳定"之后,我们会看到如下面的图中很多的空白区域。
在这里插入图片描述

而其中的840ms到852ms就是一个渲染周期,由于此时已经结束了前期很繁重的任务,所以会看到中间会有很多的空闲时间,这段时间,我们的浏览器就很"空闲".此时的时间,由于是还有些许的任务,所以一个渲染周期我们可以理解为12ms一次渲染。
到了后期,页面更加稳定了,基本很久都不动了,这时候我们就会发现,页面的渲染周期更长了,有50ms,并且空闲的时间更多了,大概有49ms

在这里插入图片描述

个人建议好好去了解下前端的微任务以及宏任务,一个渲染周期等,我觉得这个我可以水一篇博客(0.0)

关于requestIdleCallback

window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。
假设,我们的项目中有不少的琐碎的且优先级比较低的一些操作,比如是错误上报。
错误上报是一个不怎么重要的事情,因为我们只需要上报出错误,不需要管回来的数据是什么,所以只需要几ms就可以了,而这个事件就可以放在我们的空闲时间中。
当然,我本文的例子中不会使用错误上报来做例子,毕竟只有几毫秒,我们很难直接看到效果,所以我会选择在输入框的input时间做一些耗时的操作。

准备代码

首先,来一段代码,内容很简单,一个TextArea,当触发input事件的时候,会做一串的操作,而每次的操作时间都超过50ms,模拟一个比较耗时的操作。

而由于每次都超过50ms,所以到了后面,每次输入都会很卡,甚至会让页面卡死。

<html>
  <title>测试requestIdleCallback</title>
  <body>
    <textarea
      class="textarea"
      rows="10"
      cols="40"
      oninput="handleChange(this)"
      placeholder="请在这儿输入"
    ></textarea>
    <div class="logDiv"></div>
  </body>
  <script>
    // 假的敏感词数组。
    const arr = new Array(1000 * 1000).fill(1).map((v, index) => index);
    const sendList = [];
    // 触发事件
    function handleChange(e) {
      sendList.push(e.value);
      sendRequest();
    }
    // 假装发送请求校验。
    function sendRequest() {
      if (sendList.length > 0) {
        const value = sendList[0];
        const startTime = +new Date();
        // 两个耗时的map事件
        arr.map((num) => num == value);
        arr.map((num) => num == value);
?
        const endTime = +new Date();
        console.log(`time:${endTime - startTime}`);
        sendList.shift();
      }
    }
</script>
</html>
?

一般来说,我们是会在用户最终提交数据的时候才做敏感词校验,但是这儿是做一个示例,所以请不要介意这么多,也不要考虑防抖与节流。
在这里插入图片描述

如上图,我一直按着"1",但是由于一直触发着sendRequest的事件,所以页面并没有很顺畅,甚至在后期的时候很卡顿。

requestIdleCallback的语法?

前面已经讲了,requestIdleCallback的用途是将一些不怎么重要的事件放在空闲时间中去运行。接下来先讲解下相关的语法。

requestIdleCallback(callback)
requestIdleCallback(callback, options)

requestIdleCallback需要传入的一个必然参数是callback,也就是我们想要在空闲时间调用的那个参数,options 是一个可选的参数,且只有一个timeout的参数,示例的代码如下

requestIdleCallback(
  (deadline) => {
    if (deadline.timeRemaining() > 16.7 || deadline.didTimeout) 
    { }
  }
  , { timeout: 60 * 1000 }
)

在调用的函数中,获取到了一个对象–deadline,且含有以下两个属性

  • timeout
    回调在 timeout 毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。

  • didTimeout
    一个布尔值,如果回调是因为超过了设置的超时时间而被执行的,则其值为 true。

  • timeRemaining
    返回一个浮点数字,用来表示当前闲置周期的预估剩余毫秒数。如果闲置期已经结束,则其值为 0。你的回调函数可以重复调用该函数,以判断目前是否有足够的时间来执行更多的任务。

既然了解了requestIdleCallback的用法,现在就将其结合在我们的项目中。
至于前面的16.7,是因为下面 的说法,但是不是绝对的。

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
结合上面,我们的代码如下

<html>
  <title>测试requestIdleCallback</title>
  <body>
    <textarea
      class="textarea"
      rows="10"
      cols="40"
      oninput="handleChange(this)"
      placeholder="请在这儿输入"
    ></textarea>
    <div class="logDiv"></div>
  </body>
  <script>
    // 假的敏感词数组。
    const arr = new Array(1000 * 1000).fill(1).map((v, index) => index);
    const sendList = [];
    // 触发事件
    function handleChange(e) {
      sendList.push(e.value);
      sendRequest();
    }
    // 假装发送请求校验。
    function sendRequest() {
      requestIdleCallback(
        (deadline) => {
          if (
            // 当前的空余时间不超过16.7s,并且不是被强制执行的。
            (deadline.timeRemaining() > 16.7 || deadline.didTimeout) &&
            sendList.length > 0
          ) {
            const value = sendList[0];
            const startTime = +new Date();
            // 两个耗时的map事件
            arr.map((num) => num == value);
            arr.map((num) => num == value);
?
            const endTime = +new Date();
            console.log(`time:${endTime - startTime}`);
            sendList.shift();
          }
          // 判断是否有时间可以继续进行循环
          sendList.length > 0 && requestIdleCallback(sendRequest);
        },
        { timeout: 60 * 1000 }
      );
    }
</script>
</html>

?
sendRequest 代码的逻辑如下:

当事件触发的时候,我们会调用该API,将事情放在空闲时间中去执行,判断当前的时间是否是大于16.7ms,这个根据每个项目来判定,因为不同的情况会有不同的判断,而此时我判断16.7ms,是为了让其在50ms的真正空闲时间内执行。
当我们在空余时间执行了之后,会再次调用一次事件,这样子会继续在调用一次,看本次是否还剩余时间足够我们进行操作。
如果在60s内,事件没有被调用过,那么就会强制执行!

效果如下

在这里插入图片描述

可以看到页面已经好了很多,并且页面没有什么大问题了,只是页面还是有点卡顿,这是因为我们的页面到了后期的时候还是在一直发起sendRequest方法,所以页面就有点卡,但是比之前好多了。

为啥是鸡肋

我想,通过了上面的例子,大家都理解了这个API的方法是怎么用的了,但是需要注意,我们是将任务堆放在了空闲时间中,而空闲时间一般不超过16.7ms,我们很难知道16.7ms内能做什么事情,尤其是我们很少有这么低优先级以及这么琐碎的任务。
但是对于一些框架来说,这个API可以做一些架构级别的处理,比如React的fiber就根据这个Api解决了一些小问题,并且重构了Vdom树的结构,下一篇博客会解释。

但是!!!!

正如我前面所说,我们很难知道一些事务是否很小,可以在16.7ms内完成,若事件的时间超过16.7ms,还会影响下次的渲染,所以我们一般会选择使用settimeout来将其放在下一次渲染,而大的事件会放在后面的空闲时间。

但是!!!!

上面代码中,我们无法预测用户如何停止输入文本框的事情,所以我们无法使用settimeout去指定一个时间执行业务,而此时,这个API就显得很重要,因为我们可以知道页面什么时候真的"空闲",不过还是那句话,除非我们构建自己的框架,不然很少用到
且不少同学会不小心的在这儿更新一些页面的操作,所以就很不友好,毕竟很少有可以预测的小任务。
这个API,而且在兼容性上也有问题,所以在百度等网站中,我们会看到下面的代码

在这里插入图片描述

this.requestIdleFn = window.requestIdleCallback ?
  window.requestIdleCallback.bind(window) :
  function (e) {
    var t = Date.now();
    return setTimeout(function () {
      e({
        didTimeout: !1,
        timeRemaining: function () {
          return Math.max(0, 16 - (Date.now() - t))
        }
      })
    }, 1)
  }

最后的废话

一定要小心,要是requestIdleCallback中放入了执行时间较长的任务,会导致页面出现卡顿, 也就是我们所说的"丢帧".

建议无聊时间看看岛田庄司的<屋顶上的小丑>,书的结构还不错。

====================================================
前端开发的博客,偶尔写一些历史的整理,由衷期望各位大佬们扫码关注

在这里插入图片描述

公众号文章

文章来源:https://blog.csdn.net/HUSHILIN001/article/details/135502932
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。