当浏览器中执行一些计算密集型或耗时的任务时,这可能会导致页面响应变慢,因为当前窗口的JS线程和渲染线程(GUI)同一时间只能执行一个,如果JS线程一直在执行,那么页面就不能渲染和响应了,意味着动画停止、用户事件无法响应,详细的可以查看这篇文章,用户体验下降。可以帮助你在主线程空闲时执行一些任务,以避免阻塞用户界面。
讲解
在具体讲解之前,我们先看一个运行效果:
我们的任务是向框中 1.7万+个,来记录任务的完成情况。核心逻辑如下:
可以看到,我们的任务需要渲染的特别多。如果不做优化,即任务2的运行情况,直接往节点1.7万个,会导致页面直接卡死,动画停止,事件无法响应,主线程阻塞,上面的gif图看的很明显,点击任务2的开始时,页面卡主接近4s,而任务1完成了同样的需求,但页面很丝滑,接下来我们讲解的用法,及具体的代码优化方案。
调用方式
参数
callback必传
一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为 的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。的类型如下:
options (可选)
可选的配置参数。具有如下属性::如果指定了 ,并且有一个正值,而回调在 毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。这个时间跟上面的息息相关。
返回值
一个 ID,可以把它传入 方法来结束回调。
值得注意的问题
充分利用空闲回调,旨在为代码提供一种与事件循环协作的方式,以确保系统充分利用其潜能,不会过度分配任务,从而导致延迟或其他性能问题,因此您应该考虑如何使用它,但以下几点一定要注意。
1. 对非高优先级的任务使用空闲回调,优先极高的不适合使用
已经创建了多少回调,用户系统的繁忙程度,你的回调多久会执行一次(除非你指定了 ),这些都是未知的。不能保证每次事件循环(甚至每次屏幕更新)后都能执行空闲回调;如果事件循环用尽了所有可用时间,那你可就倒霉了(再说一遍,除非你用了 )。
2. 空闲回调应尽可能不超支分配到的时间
尽管即使你超出了规定的时间上限,通常来说浏览器、代码、网页也能继续正常运行,这里的时间限制是用来保证系统能留有足够的时间去完成当前的事件循环然后进入下一个循环,而不会导致其他代码卡顿或动画效果延迟。目前, 有一个 50 ms 的上限时间,但实际上你能用的时间比这个少,因为在复杂的页面中事件循环可能已经花费了其中的一部分,浏览器的扩展插件也需要处理时间,等等
3. 避免在空闲回调中改变 DOM
空闲回调执行的时候,当前帧已经结束绘制了,所有布局的更新和计算也已经完成。如果你做的改变影响了布局,你可能会强制停止浏览器并重新计算,而从另一方面来看,这是不必要的。如果你的回调需要改变 DOM,它应该使用来调度它
4. 避免运行时间无法预测的任务
你的空闲回调必须避免做任何占用时间不可预测的事情。比如说,应该避免做任何会影响页面布局的事情。你也必须避免 执行 的和,因为这会在你的回调函数返回后立即引用 对象对和的处理程序。
5. 在你需要的时候要用 timeout,但记得只在需要的时候才用
使用 timeout 可以保证你的代码按时执行,但是在剩余时间不足以强制执行你的代码的同时保证浏览器的性能表现的情况下,timeout 就会造成延迟或者动画不流畅。
总结一下:只有优先级没那么高的任务,你才用来执行,执行的任务耗时不要超过,不要执行需要改变dom的任务,非要有就需要用来调度,避免执行任务,必要时,为了保证任务按时执行,可以配置参数。
优化文章开头的需求
管理任务队列
上面主要是为了构造我们的: ,传入了我们需要执行的回调和。
执行任务
我们的空闲回调处理方法,,将在浏览器确定有足够的可用空闲时间让我们做一些我们的工作时,或者 1 秒的timeout到期时被调用。这个方法的作用是执行队列中的任务。
的核心是一个循环,只要有剩余时间(通过检查来确认它大于 0),或者已经达到了 期限( (en-US)值为真),且任务列表中有任务就会一直持续。
对队列中每个我们有时间执行的任务,我们做以下操作:
把任务对象(object)从队列中移除。
我们让增加来追踪我们已执行的任务数量。
我们调用任务处理方法,,并任务的数据对象()传入其中。
我们调用一个方法,,去处理调度一个屏幕更新来体现我们进度的变化。没有空闲了,还有任务,则继续调度。直到为空,则代表所有任务执行完毕。
更新状态显示
我们想要能够做的一件事是根据记录输出和进度信息来更新文档。然后在空闲回调中改变 DOM 是不安全的。作为替代,我们使用 来让浏览器在可以安全地更新显示时通知我们。
更新显示
函数负责绘制进度框的内容和记录。当 DOM 的状况安全,我们可以在下次渲染过程中申请改变时,浏览器会调用它。
向记录添加文本
函数可以向记录中添加指定的文本。因为我们不知道调用 的时候是否可以立即安全地更新 DOM,我们将缓存记录文本一直到可以安全更新。在上面,在 的代码中,你可以找到更新动画帧时,实际添加记录的代码。
首先,如果当前不存在一个名为的 对象。该元素是伪 DOM,我们可以在其中插入元素,而无需立即更改主 DOM 本身。
然后我们创建一个新的元素,并将其内容设置为与输入文本匹配。接下来我们向中的伪 DOM 末尾添加一个新的元素。将会累积记录条目直到下次因 DOM 改变而调用的时候。
任务处理器
,将是我们用来作为任务处理器的函数,也是用作任务对象属性的值。它是一个简单的为每个任务向记录输出大量内容的函数。
兼容性
可以看大,Safari是全军覆没的,不过没有有关系,我们同样可以模拟实现一个
上面的代码使用模拟了一个,可以看出,当闭包记录的时间和执行的时间大于,则timeRemaining()返回0,则代表我们的回调还是要马上执行。不过值得注意的是它并不是完全的.它只是让你的代码延时执行来达到缓解浏览器压力的效果,它并不能准确的在浏览器空闲时执行我们的回调。
总结
使用优化我们的程序,不用像其他常规方法一样,比如说减少频繁。而是拼接起来,如:
然后再一起。这样可以减少浏览器的渲染次数,大大减少卡顿。而它会在适当的时候告诉你浏览器有空闲了,你可以执行你的任务了,但是如果你的任务有操作,你需要把它放到中。
另一种优化方式是使用来优化,但是有很多局限,不能操作,不能直接与主进程通讯,需要通过事件的方式,详细的可以查看这篇文章。
完整的代码我贴到下面了,比较长,关键的地方我都写了注释,可以直接拷贝到html文件运行: