大家好,我是渡一前端子辰老师。
在前端开发中,咱们经常会遇到一些高档量的使命和数据,比如烘托大量的列表、处理复杂的事务逻辑、履行耗时的核算等。
这些使命假如不加以优化,很容易导致页面卡顿或响应速度变慢,严重影响用户体会。
那么,咱们该怎么优化这些高档量使命的履行呢?有没有一些通用的办法和技巧呢?
本文将经过一个实践的面试题来展现和剖析不同的使命调度和页面烘托机制,以及怎么使用微使命、宏使命、requestAnimationFrame 和 requestIdleCallback 等技能来完结高档量使命履行的优化。
希望你能从中受益,并在你的项目中运用起来。
温馨提示:请同学们自备温水,由于这篇文章太干了!
面试题
这是一个十分有诚心的面试题,为了让你做题,还专门写了一个页面。
咱们再来看一下面试题要求。
/**
* 运转一个耗时使命
* 假如要异步履行使命,请回来Promise
* 要赶快完结使命,一起不要让页面发生卡顿
* 尽量兼容更多的浏览器
* @param {Function} task
*/
function runTask(task) {}
他的要求是让咱们完结一个函数,当点击按钮的时分会履行 500 个耗时使命,其实便是调用 500 次这个 task()
函数。
那么咱们该怎么完结这个函数呢?让咱们一起来探究一下。
解题
同步履行
最简略直接的办法便是同步履行这些使命,也便是直接调用 task() 函数。
function runTask(task) {
task();
}
当然假如你仅仅这么做的话,结果便是下图这样的。
你会看到,点击按钮开端履行使命的时分,动画出现了堵塞,履行结束后显示耗时 2500 毫秒,所以直接履行肯定是不行的。
堵塞的原因也很简略,由于 task 是同步使命,由于它比较耗时,履行 500 次,所以就堵塞了页面的烘托。
已然直接履行不行,咱们试试异步的方式。
题目要求说:假如要异步履行使命,请回来 Promise。
微使命
咱们依照他的要求写了,并将使命放在微行列里履行。
function runTask(task) {
return new Promise((resolve) => {
Promise.resolve().then(() => {
task();
resolve();
});
});
}
再看下作用怎么。
你会发现微使命仍是堵塞,微使命堵塞其实也很好了解,是由于在事情循环里边,微行列一定要全部清空才能做后边的工作,当然也包括烘托。
咱们知道浏览器大概每 16.6 毫秒烘托一次页面(详细时刻取决于屏幕刷新率),由于中心有 500 个微使命,那么烘托帧就要就被推迟了,只能等到微使命全部完结之后才能够从头烘托,所以说也会看到堵塞。
已然微使命也不行,咱们换成 setTimeout。
setTimeout 依照过去的说法会发生宏使命,这个使命会进入宏行列;依照最新的说法是发生延时使命,这个使命会进入延时行列。
不管怎么说,setTimeout 的作用都是类似的。
宏使命
function runTask(task) {
return new Promise((resolve) => {
setTimeout(() => {
task();
resolve();
}, 0);
});
}
咱们再来看一下 setTimeout 的作用怎么。
你会发现没有发生堵塞,可是变得卡顿了。
那么宏使命为什么会形成卡顿而不是堵塞呢?咱们来看一下宏使命是怎么履行的。
同学们应该都知道,事情循环实践上是一个死循环,咱们单说宏使命在这个循环里做了什么工作。
for (; ;) {
1 - 取出宏行列中第一个(最早加入)的宏使命并履行
2 - 履行使命
3 - 判别是否到达烘托机遇,能够简略的了解为到了 16.6 毫秒就进行烘托
}
宏使命不堵塞的原因便是由于宏使命每次只取一个使命履行,履行完了之后假如说烘托机遇到了之后就烘托,然后下一次循环再取下一个使命,所以它就不会堵塞。
至于卡顿的原因就比较有意思了,这是由于不同的浏览器来判别这个烘托机遇的方式不同,W3C 并没有十分清晰的规则这个烘托机遇,所以很多时分需求浏览器自己去判别。
那么每个浏览器的判别方式是有差异的,比如谷歌浏览器就以为,行列里有十分多的使命了,有很高的核算需求了,它觉得要匀一部分核算资源给这些正在等候的宏使命,所以会稍微的把这个烘托机遇向后延一延,Edge 浏览器和谷歌大同小异,这便是界面卡顿的原因。
咱们回归正题,现在 setTimeout 会形成卡顿,也不能用,那咱们试试 requestAnimationFrame 办法。
requestAnimationFrame
requestAnimationFrame 是一个专门用于动画烘托的办法,它会在浏览器下一次重绘之前履行回调函数。咱们能够使用这个办法来履行使命。
function runTask(task) {
return new Promise((resolve) => {
requestAnimationFrame(() => {
task();
resolve();
});
});
}
咱们再来看一下 requestAnimationFrame 的作用怎么。
很明显也堵塞了,由于在烘托帧之前,履行了 500 次耗时使命,每一次都把这个烘托帧向后延了,所以现在 requestAnimationFrame 也不能够用。
想来想去咱们就只能手动的操控这个使命的履行机遇了,不能简略粗暴的放在这个函数里履行了,咱们得找到一种办法,在这个使命履行之前判别一下现在运转是否合适,不合适的话在等一会,那么咱们就得写一个辅助函数了。
requestIdleCallback
那么思路有了,咱们依据什么判别是否合适运转呢?咱们现在要不堵塞且不形成卡顿,是不是就要看烘托帧是否有剩余时刻,判别 16.6 毫秒一帧还剩下多少时刻能够供咱们操作,所以假如有时刻就履行,假如没有时刻了就等一会。
这个时分你就应该想到一个办法,叫做 requestIdleCallback,requestIdleCallback 办法插入一个函数,这个函数将在浏览器闲暇时期被调用,刚好契合咱们的想象,咱们去试一试。
function _runTask(task, callback) {
// 使用 requestIdleCallback 办法,回来一个参数 idle 目标
// idle 中有一个办法 timeRemaining,能够获取到剩余时刻
requestIdleCallback((idle) => {
// 假如有闲暇时刻就调用反之则递归继续等候
if (idle.timeRemaining() > 0) {
task();
callback();
} else {
_runTask(task, callback);
}
});
}
function runTask(task) {
return new Promise((resolve) => {
_runTask(task, resolve);
});
}
作用呢便是如此丝滑,当然使命有时分会等候,所以使命完结时刻也会相对的延伸。
到这呢已经十分的不错了,可是呢面试题里的最终一条要求咱们尽量兼容更多的浏览器,而 requestIdleCallback 兼容性确不是十分好,咱们去 caniuse 看看兼容性。
能够看到,requestIdleCallback 的兼容性并非十分的好。
咱们在看看 requestAnimationFrame 的兼容性。
那咱们就把 requestIdleCallback 换成 requestAnimationFrame。
function _runTask(task, callback) {
// 经过 Date 的时差,记录一个时刻
let start = Date.now();
requestAnimationFrame(() => {
// 核算一下剩余时刻
if (Date.now() - start < 16.6) {
task();
callback();
} else {
_runTask(task, callback);
}
});
}
function runTask(task) {
return new Promise((resolve) => {
_runTask(task, resolve);
});
}
能够看到十分的流畅了,只不过耗费的时刻同样也会延伸。
总结
今日主要便是处理怎么优化高档量使命履行的办法和技巧,经过一个实践的面试题来展现了不同的使命调度和页面烘托机制的作用和原理。
其中包括同步使命、微使命、宏使命、requestAnimationFrame 和 requestIdleCallback 等技能。
子辰还剖析了每种办法的优缺点,希望你能够从这篇文章中学到前端开发中的使命调度和页面烘托机制的基本概念和原理,以及怎么使用现有的技能来优化高档量使命履行的办法和技巧。
本文来历
本文来历自渡一官方大众号:Duing,欢迎关注,获取最新、最全、最深化的技能解说
感谢你阅览本文,假如你有任何疑问或建议,请在谈论区留言,假如你觉得这篇文章有用,请点赞保藏或分享给你的朋友