文档章节

利用好浏览器的空闲时间 --- requestIdleCallback

o
 osc_a22drz29
发布于 2019/03/27 23:20
字数 1281
阅读 13
收藏 0

<table class="d-block"> <tbody class="d-block"> <tr class="d-block"> <td class="d-block comment-body markdown-body js-comment-body">

<h3>页面流畅与 FPS</h3> <p>页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。</p> <p>1s 60帧,所以每一帧分到的时间是 1000/60 ≈ 16 ms。所以我们书写代码时力求不让一帧的工作量超过 16ms。</p> <h3>Frame</h3> <p>那么浏览器每一帧都需要完成哪些工作?</p> <p><a target="_blank" rel="noopener noreferrer" href="https://user-images.githubusercontent.com/3783096/51423451-4a5f1f80-1bfb-11e9-8c0a-597f0d52f4c0.png"><img src="https://user-images.githubusercontent.com/3783096/51423451-4a5f1f80-1bfb-11e9-8c0a-597f0d52f4c0.png" alt="image" style="max-width:100%;"></a><br> <em>浏览器一帧内的工作</em></p> <p>通过上图可看到,一帧内需要完成如下六个步骤的任务:</p> <ul> <li>处理用户的交互</li> <li>JS 解析执行</li> <li>帧开始。窗口尺寸变更,页面滚去等的处理</li> <li>rAF</li> <li>布局</li> <li>绘制</li> </ul> <h3>requestIdleCallback</h3> <p>上面六个步骤完成后没超过 16 ms,说明时间有富余,此时就会执行 <code>requestIdleCallback</code> 里注册的任务。</p> <p><a target="_blank" rel="noopener noreferrer" href="https://user-images.githubusercontent.com/3783096/51231779-9b72d780-199f-11e9-8e65-eb9df90921ce.png"><img src="https://user-images.githubusercontent.com/3783096/51231779-9b72d780-199f-11e9-8e65-eb9df90921ce.png" alt="image" style="max-width:100%;"></a><br> <em><code>requestIdleCallback</code> 在浏览器一帧内的位置示意</em></p> <p>从上图也可看出,和 <code>requestAnimationFrame</code> 每一帧必定会执行不同,<code>requestIdleCallback</code> 是捡浏览器空闲来执行任务。</p> <p>如此一来,假如浏览器一直处于非常忙碌的状态,<code>requestIdleCallback</code> 注册的任务有可能永远不会执行。此时可通过设置 <code>timeout</code> (见下面 API 介绍)来保证执行。</p> <h4>API</h4> <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> handle <span class="pl-k">=</span> <span class="pl-c1">window</span>.<span class="pl-en">requestIdleCallback</span>(callback[, options])</pre></div> <ul> <li><code>callback: ()</code>:回调即空闲时需要执行的任务,接收一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline" rel="nofollow"><code>IdleDeadline</code></a> 对象作为入参。其中 <code>IdleDeadline</code> 对象包含: <ul> <li><code>didTimeout</code>,布尔值,表示任务是否超时,结合 <code>timeRemaining</code> 使用。</li> <li><code>timeRemaining()</code>,表示当前帧剩余的时间,也可理解为留给任务的时间还有多少。</li> </ul> </li> <li><code>options</code>:目前 options 只有一个参数 <ul> <li><code>timeout </code>。表示超过这个时间后,如果任务还没执行,则强制执行,不必等待空闲。</li> </ul> </li> </ul> <h4>示例</h4> <div class="highlight highlight-source-js"><pre><span class="pl-en">requestIdleCallback</span>(myNonEssentialWork, { timeout<span class="pl-k">:</span> <span class="pl-c1">2000</span> });

<span class="pl-k">function</span> <span class="pl-en">myNonEssentialWork</span> (<span class="pl-smi">deadline</span>) {

  <span class="pl-c"><span class="pl-c">//</span> 如果帧内有富余的时间,或者超时</span>   <span class="pl-k">while</span> ((<span class="pl-smi">deadline</span>.<span class="pl-en">timeRemaining</span>() <span class="pl-k">></span> <span class="pl-c1">0</span> <span class="pl-k">||</span> <span class="pl-smi">deadline</span>.<span class="pl-smi">didTimeout</span>) <span class="pl-k">&&</span>          <span class="pl-smi">tasks</span>.<span class="pl-c1">length</span> <span class="pl-k">></span> <span class="pl-c1">0</span>)     <span class="pl-en">doWorkIfNeeded</span>();

  <span class="pl-k">if</span> (<span class="pl-smi">tasks</span>.<span class="pl-c1">length</span> <span class="pl-k">></span> <span class="pl-c1">0</span>)     <span class="pl-en">requestIdleCallback</span>(myNonEssentialWork); }</pre></div>

<p>超时的情况,其实就是浏览器很忙,没有空闲时间,此时会等待指定的 <code>timeout</code> 那么久再执行,通过入参 <code>dealine</code> 拿到的 <code>didTmieout</code> 会为 <code>true</code>,同时 <code>timeRemaining ()</code> 返回的也是 0。超时的情况下如果选择继续执行的话,肯定会出现卡顿的,因为必然会将一帧的时间拉长。</p> <h4>cancelIdleCallback</h4> <p>与 <code>setTimeout</code> 类似,返回一个唯一 id,可通过 <code>cancelIdleCallback</code> 来取消任务。</p> <h3>总结</h3> <p>一些低优先级的任务可使用 <code>requestIdleCallback</code> 等浏览器不忙的时候来执行,同时因为时间有限,它所执行的任务应该尽量是能够量化,细分的微任务(micro task)。</p> <p>因为它发生在一帧的最后,此时页面布局已经完成,所以不建议在 <code>requestIdleCallback</code> 里再操作 DOM,这样会导致页面再次重绘。DOM 操作建议在 rAF 中进行。同时,操作 DOM 所需要的耗时是不确定的,因为会导致重新计算布局和视图的绘制,所以这类操作不具备可预测性。</p> <p>Promise 也不建议在这里面进行,因为 Promise 的回调属性 Event loop 中优先级较高的一种微任务,会在 <code>requestIdleCallback</code> 结束时立即执行,不管此时是否还有富余的时间,这样有很大可能会让一帧超过 16 ms。</p> <h3>参考</h3> <ul> <li><a href="https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4" rel="nofollow">requestAnimationFrame Scheduling For Nerds</a></li> <li><a href="https://developers.google.com/web/fundamentals/performance/rendering/" rel="nofollow">Rendering Performance</a></li> <li><a href="https://juejin.im/post/5ad71f39f265da239f07e862" rel="nofollow">你应该知道的requestIdleCallback</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback" rel="nofollow">MDN window.requestIdleCallback()</a></li> <li><a href="https://developers.google.com/web/updates/2015/08/using-requestidlecallback" rel="nofollow">Using requestIdleCallback</a></li> </ul> </td> </tr> </tbody> </table>

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。

暂无文章

树莓派4b + Ubuntu20.04 Server 安装Java8

安装环境: 树莓派4b + Ubuntu20.04 Server 32位 1. 下载jdk https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 2. 解压 tar -zxvf jdk-8u251-linux-arm32-vf......

SummerGao
23分钟前
9
0
项目实战:Qt+OpenCV图像处理与识别算法平台

若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062 本文章博客地址:h...

红模仿_红胖子
26分钟前
7
0
北京智源大会 | AI + 医疗的下一个十年:从公共卫生预警到人类基因密码破解 道翰天琼认知智能api机器人接口。

医疗事关人身安全,要求极高,容错率极低,因此,知识壁垒和技术壁垒都很高。过去,AI系统更多的是服务于终端,辅助医生诊断、决策。但是,医疗很复杂,直接切入终端问题很多。未来十年,AI+...

jackli2020
30分钟前
11
0
源于HystrixCommandStartStream和RollingCommandMaxConcurrencyStream 的 RxJava demo

其实,最近在工作之余看Hystrix源代码已经有一个多月了, 除了对 HystrixCommandProperties ,HystrixCommand 和AbstractCommand 几个类比较了解以外,其余看山不是山,比较懵, 主要是因为H...

专业写BUG的程序员
33分钟前
16
0
聊聊Java中的异常及处理

前言 在编程中异常报错是不可避免的。特别是在学习某个语言初期,看到异常报错就抓耳挠腮,常常开玩笑说编程1分钟,改bug1小时。今天就让我们来看看什么是异常和怎么合理的处理异常吧! 异常...

良许Linux
36分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部