最近在做思考总结,项目原来在做智能合同的项目关于文档比对导致页面卡断问题,项目组同事给了我一个切片编程的方案很棒,在此做个总结!
需求
源文档和修改后的文档,差异点存在近 5000+ 的差异点需要在两个文档上进行颜色标注,整个标注过程都是在进行 DOM 操作,做完后存在严重的页面卡死问题,甚至等待 5~8分钟才标注完成,甚至卡死!这个问题亟待解决优化!
分析
在事件循环机制中主线程需要全部执行完才会进行页面渲染,5000+ 的 DOM 操作严重阻塞了主线程,所以是不是可以给主线程让出空间,将dom操作任务放进宏任务队列,在下一次渲染之后继续执行DOM操作呢?
思路
1、执行主线程,DOM操作(先进行20次dom操作,剩余的任务放进setTimeout宏任务)
2、页面渲染,显示差异对比的颜色标注(上一次dom操作的结果)
3、执行宏任务中 20个 DOM 操作任务
4、页面渲染
5、重复 3、4
最后是结了这个方案,5000+ 的dom操作还是蛮顺滑的!最后我们讲讲时间切片思想
什么是时间切片
时间切片的核心思想是:如果任务不能在50毫秒内执行完,那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务。让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随后再回来继续执行没有执行完的任务。
所以时间切片的目的是不阻塞主线程,而实现目的的技术手段是将一个长任务拆分成很多个不超过50ms的小任务分散在宏任务队列中执行。
上图可以看到主线程中有一个长任务,这个任务会阻塞主线程。使用时间切片将它切割成很多个小任务后,如下图所示。
可以看到现在的主线程有很多密密麻麻的小任务,我们将它放大后如下图所示。
可以看到每个小任务中间是有空隙的,代表着任务执行了一小段时间后,将让出主线程的控制权,让浏览器执行其他的任务。
使用时间切片的缺点是,任务运行的总时间变长了,这是因为它每处理完一个小任务后,主线程会空闲出来,并且在下一个小任务开始处理之前有一小段延迟。
但是为了避免卡死浏览器,这种取舍是很有必要的。
如何使用时间切片
目前发现有两种解决方案,不过起主体还是通过让出主线程的控制权
1、setTimeout
2、requestAnimationFrame
3、setTimeout + Generator
方案一:setTimeout
function request() {
let result = [];
for (let i = 0; i < 100000; i++) {
result.push({
index: i,
text: ~~(Math.random() * 100000000),
});
}
return Promise.resolve(result);
}
let ul = document.querySelector("#app");
request().then((res) => {
let total = res.length;
let once = 20;
let page = total / once;
let curIndex = 0;
function loop(curTotal, curIndex) {
let pageCount = Math.min(curTotal , once);
setTimeout(function () {
let fragement = document.createDocumentFragment();
for (let i = 0; i < pageCount; i++) {
let li = document.createElement("li");
li.innerText =
"index: " + res[curIndex + i].index + "; text: " + res[curIndex + i].text;
fragement.appendChild(li);
}
ul.appendChild(fragement);
loop(curTotal - pageCount, curIndex + pageCount);
});
}
loop(total, curIndex);
}
简单聊一下 setTimeout 和闪屏现象
- setTimeout的执行时间并不是确定的。在JS中,setTimeout任务被放进事件队列中,只有主线程执行完才会去检查事件队列中的任务是否需要执行,因此setTimeout的实际执行时间可能会比其设定的时间晚一些。
- 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的刷新频率可能会不同,而setTimeout只能设置一个固定时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致。
在setTimeout中对dom进行操作,必须要等到屏幕下次绘制时才能更新到屏幕上,如果两者步调不一致,就可能导致中间某一帧的操作被跨越过去,而直接更新下一帧的元素,从而导致丢帧现象。
方案二:requestAnimationFrame
与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。
如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。
function request() {
let result = [];
for (let i = 0; i < 100000; i++) {
result.push({
index: i,
text: ~~(Math.random() * 100000000),
});
}
return Promise.resolve(result);
}
let ul = document.querySelector("#app");
request().then((res) => {
let total = res.length;
let once = 20;
let page = total / once;
let curIndex = 0;
function loop(curTotal, curIndex) {
let pageCount = Math.min(curTotal , once);
requestAnimationFrame(function () {
let fragement = document.createDocumentFragment();
for (let i = 0; i < pageCount; i++) {
let li = document.createElement("li");
li.innerText =
"index: " + res[curIndex + i].index + "; text: " + res[curIndex + i].text;
fragement.appendChild(li);
}
ul.appendChild(fragement);
loop(curTotal - pageCount, curIndex + pageCount);
});
}
loop(total, curIndex);
}
方案三:setTimeout + Generator
function request() {
let result = [];
for (let i = 0; i < 100000; i++) {
result.push({
index: i,
text: ~~(Math.random() * 100000000),
});
}
return Promise.resolve(result);
}
let ul = document.querySelector("#app");
request().then((res) => {
function ts(gen) {
if (typeof gen === "function") gen = gen();
if (!gen || typeof gen.next !== "function") return;
return function next() {
const res = gen.next();
if (res.done) return;
requestAnimationFrame(next);
};
}
let total = res.length;
let once = 20;
let page = total / once;
let curIndex = 0;
ts(addDom(total))();
function* addDom(curTotal) {
while (curTotal > 0) {
let pageCount = Math.min(curTotal , once);
let fragement = document.createDocumentFragment();
for (let i = 0; i < pageCount; i++) {
let li = document.createElement("li");
li.innerText =
"index: " +
res[curIndex + i].index +
"; text: " +
res[curIndex + i].text;
fragement.appendChild(li);
}
ul.appendChild(fragement);
curIndex = curIndex + pageCount;
curTotal = curTotal - pageCount;
yield;
}
}
参考:
「前端进阶」高性能渲染十万条数据(时间分片)
时间切片(Time Slicing)
今天的文章时间切片教程_切片软件手机版「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/81575.html