真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

「中高級前端」高性能渲染十萬條數(shù)據(jù)(時(shí)間分片)

前言

10年積累的網(wǎng)站制作、成都做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有巴青免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

在實(shí)際工作中,我們很少會(huì)遇到一次性需要向頁面中插入數(shù)十萬條數(shù)據(jù)的情況,但是為了豐富我們的知識(shí)體系,我們有必要了解并清楚當(dāng)遇到大量數(shù)據(jù)時(shí),如何才能在不卡主頁面的情況下渲染數(shù)據(jù),以及其中背后的原理。

最粗暴的做法(一次性渲染)


復(fù)制代碼
//?記錄任務(wù)開始時(shí)間
let?now?=?Date.now();
//?插入十萬條數(shù)據(jù)
const?total?=?100000;
//?獲取容器
let?ul?=?document.getElementById('container');
//?將數(shù)據(jù)插入容器中
for?(let?i?=?0;?i?{
?console.log('總運(yùn)行時(shí)間:',Date.now()?-?now);
},0)
//?print:?JS運(yùn)行時(shí)間:?187
//?print:?總運(yùn)行時(shí)間:?2844
復(fù)制代碼

我們對十萬條記錄進(jìn)行循環(huán)操作,JS的運(yùn)行時(shí)間為187ms,還是蠻快的,但是最終渲染完成后的總時(shí)間確是2844ms。

簡單說明一下,為何兩次console.log的結(jié)果時(shí)間差異巨大,并且是如何簡單來統(tǒng)計(jì)JS運(yùn)行時(shí)間和總渲染時(shí)間:

  • 在 JS 的Event Loop中,當(dāng)JS引擎所管理的執(zhí)行棧中的事件以及所有微任務(wù)事件全部執(zhí)行完后,才會(huì)觸發(fā)渲染線程對頁面進(jìn)行渲染

  • 第一個(gè)console.log的觸發(fā)時(shí)間是在頁面進(jìn)行渲染之前,此時(shí)得到的間隔時(shí)間為JS運(yùn)行所需要的時(shí)間

  • 第二個(gè)console.log是放到 setTimeout 中的,它的觸發(fā)時(shí)間是在渲染完成,在下一次Event Loop中執(zhí)行的

關(guān)于Event Loop的詳細(xì)內(nèi)容請參見這篇文章-->

依照兩次console.log的結(jié)果,可以得出結(jié)論:

對于大量數(shù)據(jù)渲染的時(shí)候,JS運(yùn)算并不是性能的瓶頸,性能的瓶頸主要在于渲染階段

使用定時(shí)器

從上面的例子,我們已經(jīng)知道,頁面的卡頓是由于同時(shí)渲染大量DOM所引起的,所以我們考慮將渲染過程分批進(jìn)行

在這里,我們使用setTimeout來實(shí)現(xiàn)分批渲染


復(fù)制代碼
//需要插入的容器
let?ul?=?document.getElementById('container');
//?插入十萬條數(shù)據(jù)
let?total?=?100000;
//?一次插入?20?條
let?once?=?20;
//總頁數(shù)
let?page?=?total/once
//每條記錄的索引
let?index?=?0;
//循環(huán)加載數(shù)據(jù)
function?loop(curTotal,curIndex){
?if(curTotal?<=?0){
?return?false;
?}
?//每頁多少條
?let?pageCount?=?Math.min(curTotal?,?once);
?setTimeout(()=>{
?for(let?i?=?0;?i?

用一個(gè)gif圖來看一下效果

「中高級前端」高性能渲染十萬條數(shù)據(jù)(時(shí)間分片)

我們可以看到,頁面加載的時(shí)間已經(jīng)非??炝?,每次刷新時(shí)可以很快的看到第一屏的所有數(shù)據(jù),但是當(dāng)我們快速滾動(dòng)頁面的時(shí)候,會(huì)發(fā)現(xiàn)頁面出現(xiàn)閃屏或白屏的現(xiàn)象

為什么會(huì)出現(xiàn)閃屏現(xiàn)象呢

首先,理清一些概念。FPS表示的是每秒鐘畫面更新次數(shù)。我們平時(shí)所看到的連續(xù)畫面都是由一幅幅靜止畫面組成的,每幅畫面稱為一幀,F(xiàn)PS是描述幀變化速度的物理量。

大多數(shù)電腦顯示器的刷新頻率是60Hz,大概相當(dāng)于每秒鐘重繪60次,F(xiàn)PS為60frame/s,為這個(gè)值的設(shè)定受屏幕分辨率、屏幕尺寸和顯卡的影響。

因此,當(dāng)你對著電腦屏幕什么也不做的情況下,大多顯示器也會(huì)以每秒60次的頻率正在不斷的更新屏幕上的圖像。

為什么你感覺不到這個(gè)變化?

那是因?yàn)槿说难劬τ幸曈X停留效應(yīng),即前一副畫面留在大腦的印象還沒消失,緊接著后一副畫面就跟上來了, 這中間只間隔了16.7ms(1000/60≈16.7),所以會(huì)讓你誤以為屏幕上的圖像是靜止不動(dòng)的。

而屏幕給你的這種感覺是對的,試想一下,如果刷新頻率變成1次/秒,屏幕上的圖像就會(huì)出現(xiàn)嚴(yán)重的閃爍, 這樣就很容易引起眼睛疲勞、酸痛和頭暈?zāi)垦5劝Y狀。

大多數(shù)瀏覽器都會(huì)對重繪操作加以限制,不超過顯示器的重繪頻率,因?yàn)榧词钩^那個(gè)頻率用戶體驗(yàn)也不會(huì)有提升。 因此,最平滑動(dòng)畫的最佳循環(huán)間隔是1000ms/60,約等于16.6ms。

直觀感受,不同幀率的體驗(yàn):

  • 幀率能夠達(dá)到 50 ~ 60 FPS 的動(dòng)畫將會(huì)相當(dāng)流暢,讓人倍感舒適;

  • 幀率在 30 ~ 50 FPS 之間的動(dòng)畫,因各人敏感程度不同,舒適度因人而異;

  • 幀率在 30 FPS 以下的動(dòng)畫,讓人感覺到明顯的卡頓和不適感;

  • 幀率波動(dòng)很大的動(dòng)畫,亦會(huì)使人感覺到卡頓。

簡單聊一下 setTimeout 和閃屏現(xiàn)象

  • setTimeout的執(zhí)行時(shí)間并不是確定的。在JS中,setTimeout任務(wù)被放進(jìn)事件隊(duì)列中,只有主線程執(zhí)行完才會(huì)去檢查事件隊(duì)列中的任務(wù)是否需要執(zhí)行,因此setTimeout的實(shí)際執(zhí)行時(shí)間可能會(huì)比其設(shè)定的時(shí)間晚一些。

  • 刷新頻率受屏幕分辨率和屏幕尺寸的影響,因此不同設(shè)備的刷新頻率可能會(huì)不同,而setTimeout只能設(shè)置一個(gè)固定時(shí)間間隔,這個(gè)時(shí)間不一定和屏幕的刷新時(shí)間相同。

以上兩種情況都會(huì)導(dǎo)致setTimeout的執(zhí)行步調(diào)和屏幕的刷新步調(diào)不一致。

在setTimeout中對dom進(jìn)行操作,必須要等到屏幕下次繪制時(shí)才能更新到屏幕上,如果兩者步調(diào)不一致,就可能導(dǎo)致中間某一幀的操作被跨越過去,而直接更新下一幀的元素,從而導(dǎo)致丟幀現(xiàn)象。

使用 requestAnimationFrame

與setTimeout相比,requestAnimationFrame最大的優(yōu)勢是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。

如果屏幕刷新率是60Hz,那么回調(diào)函數(shù)就每16.7ms被執(zhí)行一次,如果刷新率是75Hz,那么這個(gè)時(shí)間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟著系統(tǒng)的刷新步伐走。它能保證回調(diào)函數(shù)在屏幕每一次的刷新間隔中只被執(zhí)行一次,這樣就不會(huì)引起丟幀現(xiàn)象。

我們使用requestAnimationFrame來進(jìn)行分批渲染:


復(fù)制代碼
//需要插入的容器
let?ul?=?document.getElementById('container');
//?插入十萬條數(shù)據(jù)
let?total?=?100000;
//?一次插入?20?條
let?once?=?20;
//總頁數(shù)
let?page?=?total/once
//每條記錄的索引
let?index?=?0;
//循環(huán)加載數(shù)據(jù)
function?loop(curTotal,curIndex){
?if(curTotal?<=?0){
?return?false;
?}
?//每頁多少條
?let?pageCount?=?Math.min(curTotal?,?once);
?window.requestAnimationFrame(function(){
?for(let?i?=?0;?i?

看下效果

「中高級前端」高性能渲染十萬條數(shù)據(jù)(時(shí)間分片)

我們可以看到,頁面加載的速度很快,并且滾動(dòng)的時(shí)候,也很流暢沒有出現(xiàn)閃爍丟幀的現(xiàn)象。

這就結(jié)束了么,還可以再優(yōu)化么?

當(dāng)然~~

使用 DocumentFragment

先解釋一下什么是 DocumentFragment ,文獻(xiàn)引用自MDN

DocumentFragment,文檔片段接口,表示一個(gè)沒有父級文件的最小文檔對象。它被作為一個(gè)輕量版的Document使用,用于存儲(chǔ)已排好版的或尚未打理好格式的XML片段。最大的區(qū)別是因?yàn)镈ocumentFragment不是真實(shí)DOM樹的一部分,它的變化不會(huì)觸發(fā)DOM樹的(重新渲染) ,且不會(huì)導(dǎo)致性能等問題。

可以使用document.createDocumentFragment方法或者構(gòu)造函數(shù)來創(chuàng)建一個(gè)空的DocumentFragment

從MDN的說明中,我們得知DocumentFragments是DOM節(jié)點(diǎn),但并不是DOM樹的一部分,可以認(rèn)為是存在內(nèi)存中的,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁面回流。

最后修改代碼如下:


復(fù)制代碼
//需要插入的容器
let?ul?=?document.getElementById('container');
//?插入十萬條數(shù)據(jù)
let?total?=?100000;
//?一次插入?20?條
let?once?=?20;
//總頁數(shù)
let?page?=?total/once
//每條記錄的索引
let?index?=?0;
//循環(huán)加載數(shù)據(jù)
function?loop(curTotal,curIndex){
?if(curTotal?<=?0){
?return?false;
?}
?//每頁多少條
?let?pageCount?=?Math.min(curTotal?,?once);
?window.requestAnimationFrame(function(){
?let?fragment?=?document.createDocumentFragment();
?for(let?i?=?0;?i?

最后

本文更多的是提供一個(gè)思路,通過時(shí)間分片的方式來同時(shí)加載大量簡單DOM。對于復(fù)雜DOM的情況,一般會(huì)用到虛擬列表的方式來實(shí)現(xiàn),關(guān)于這一問題,會(huì)持續(xù)整理,敬請期待。

「中高級前端」高性能渲染十萬條數(shù)據(jù)(時(shí)間分片)


本文名稱:「中高級前端」高性能渲染十萬條數(shù)據(jù)(時(shí)間分片)
URL標(biāo)題:http://weahome.cn/article/iecope.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部