中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

「中高級前端」高性能渲染十萬條數據

發布時間:2020-03-24 10:02:00 來源:網絡 閱讀:474 作者:可樂程序員 欄目:web開發

前言

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

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

<ul?id="container"></ul>復制代碼//?記錄任務開始時間let?now?=?Date.now();//?插入十萬條數據const?total?=?100000;//?獲取容器let?ul?=?document.getElementById('container');//?將數據插入容器中for?(let?i?=?0;?i?<?total;?i++)?{?let?li?=?document.createElement('li');
?li.innerText?=?~~(Math.random()?*?total)
?ul.appendChild(li);
}console.log('JS運行時間:',Date.now()?-?now);
setTimeout(()=>{?console.log('總運行時間:',Date.now()?-?now);
},0)//?print:?JS運行時間:?187//?print:?總運行時間:?2844復制代碼

我們對十萬條記錄進行循環操作,JS的運行時間為187ms,還是蠻快的,但是最終渲染完成后的總時間確是2844ms。

簡單說明一下,為何兩次console.log的結果時間差異巨大,并且是如何簡單來統計JS運行時間和總渲染時間:

  • 在 JS 的Event Loop中,當JS引擎所管理的執行棧中的事件以及所有微任務事件全部執行完后,才會觸發渲染線程對頁面進行渲染

  • 第一個console.log的觸發時間是在頁面進行渲染之前,此時得到的間隔時間為JS運行所需要的時間

  • 第二個console.log是放到 setTimeout 中的,它的觸發時間是在渲染完成,在下一次Event Loop中執行的

關于Event Loop的詳細內容請參見這面文章-->

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

對于大量數據渲染的時候,JS運算并不是性能的瓶頸,性能的瓶頸主要在于渲染階段

使用定時器

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

在這里,我們使用setTimeout來實現分批渲染

<ul?id="container"></ul>復制代碼//需要插入的容器let?ul?=?document.getElementById('container');//?插入十萬條數據let?total?=?100000;//?一次插入?20?條let?once?=?20;//總頁數let?page?=?total/once//每條記錄的索引let?index?=?0;//循環加載數據function?loop(curTotal,curIndex){?if(curTotal?<=?0){?return?false;
?}?//每頁多少條
?let?pageCount?=?Math.min(curTotal?,?once);
?setTimeout(()=>{?for(let?i?=?0;?i?<?pageCount;?i++){?let?li?=?document.createElement('li');
?li.innerText?=?curIndex?+?i?+?'?:?'?+?~~(Math.random()?*?total)
?ul.appendChild(li)
?}
?loop(curTotal?-?pageCount,curIndex?+?pageCount)
?},0)
}
loop(total,index);
復制代碼

用一個gif圖來看一下效果

「中高級前端」高性能渲染十萬條數據


我們可以看到,頁面加載的時間已經非常快了,每次刷新時可以很快的看到第一屏的所有數據,但是當我們快速滾動頁面的時候,會發現頁面出現閃屏或白屏的現象

為什么會出現閃屏現象呢

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

大多數電腦顯示器的刷新頻率是60Hz,大概相當于每秒鐘重繪60次,FPS為60frame/s,為這個值的設定受屏幕分辨率、屏幕尺寸和顯卡的影響。

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

為什么你感覺不到這個變化?

那是因為人的眼睛有視覺停留效應,即前一副畫面留在大腦的印象還沒消失,緊接著后一副畫面就跟上來了, 這中間只間隔了16.7ms(1000/60≈16.7),所以會讓你誤以為屏幕上的圖像是靜止不動的。

而屏幕給你的這種感覺是對的,試想一下,如果刷新頻率變成1次/秒,屏幕上的圖像就會出現嚴重的閃爍, 這樣就很容易引起眼睛疲勞、酸痛和頭暈目眩等癥狀。

大多數瀏覽器都會對重繪操作加以限制,不超過顯示器的重繪頻率,因為即使超過那個頻率用戶體驗也不會有提升。 因此,最平滑動畫的最佳循環間隔是1000ms/60,約等于16.6ms。

直觀感受,不同幀率的體驗:

  • 幀率能夠達到 50 ~ 60 FPS 的動畫將會相當流暢,讓人倍感舒適;

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

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

  • 幀率波動很大的動畫,亦會使人感覺到卡頓。

簡單聊一下 setTimeout 和閃屏現象

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

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

以上兩種情況都會導致setTimeout的執行步調和屏幕的刷新步調不一致。

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

使用 requestAnimationFrame

與setTimeout相比,requestAnimationFrame最大的優勢是由系統來決定回調函數的執行時機。

如果屏幕刷新率是60Hz,那么回調函數就每16.7ms被執行一次,如果刷新率是75Hz,那么這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟著系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引起丟幀現象。

我們使用requestAnimationFrame來進行分批渲染:

<ul?id="container"></ul>復制代碼//需要插入的容器let?ul?=?document.getElementById('container');//?插入十萬條數據let?total?=?100000;//?一次插入?20?條let?once?=?20;//總頁數let?page?=?total/once//每條記錄的索引let?index?=?0;//循環加載數據function?loop(curTotal,curIndex){?if(curTotal?<=?0){?return?false;
?}?//每頁多少條
?let?pageCount?=?Math.min(curTotal?,?once);?window.requestAnimationFrame(function(){?for(let?i?=?0;?i?<?pageCount;?i++){?let?li?=?document.createElement('li');
?li.innerText?=?curIndex?+?i?+?'?:?'?+?~~(Math.random()?*?total)
?ul.appendChild(li)
?}
?loop(curTotal?-?pageCount,curIndex?+?pageCount)
?})
}
loop(total,index);
復制代碼

看下效果

「中高級前端」高性能渲染十萬條數據


我們可以看到,頁面加載的速度很快,并且滾動的時候,也很流暢沒有出現閃爍丟幀的現象。

這就結束了么,還可以再優化么?

當然~~

使用 DocumentFragment

先解釋一下什么是 DocumentFragment ,文獻引用自MDN

DocumentFragment,文檔片段接口,表示一個沒有父級文件的最小文檔對象。它被作為一個輕量版的Document使用,用于存儲已排好版的或尚未打理好格式的XML片段。最大的區別是因為DocumentFragment不是真實DOM樹的一部分,它的變化不會觸發DOM樹的(重新渲染) ,且不會導致性能等問題。

可以使用document.createDocumentFragment方法或者構造函數來創建一個空的DocumentFragment

從MDN的說明中,我們得知DocumentFragments是DOM節點,但并不是DOM樹的一部分,可以認為是存在內存中的,所以將子元素插入到文檔片段時不會引起頁面回流。

最后修改代碼如下:

<ul?id="container"></ul>復制代碼//需要插入的容器let?ul?=?document.getElementById('container');//?插入十萬條數據let?total?=?100000;//?一次插入?20?條let?once?=?20;//總頁數let?page?=?total/once//每條記錄的索引let?index?=?0;//循環加載數據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?<?pageCount;?i++){?let?li?=?document.createElement('li');
?li.innerText?=?curIndex?+?i?+?'?:?'?+?~~(Math.random()?*?total)
?fragment.appendChild(li)
?}
?ul.appendChild(fragment)
?loop(curTotal?-?pageCount,curIndex?+?pageCount)
?})
}
loop(total,index);
復制代碼

「中高級前端」高性能渲染十萬條數據



向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

沅江市| 桂阳县| 柯坪县| 商丘市| 斗六市| 新巴尔虎右旗| 霸州市| 敖汉旗| 仙居县| 化州市| 增城市| 闸北区| 奈曼旗| 安乡县| 中宁县| 福泉市| 霞浦县| 大城县| 永定县| 台安县| 湖南省| 鄂尔多斯市| 旺苍县| 柳林县| 九江市| 平果县| 伽师县| 广平县| 天长市| 姜堰市| 宁都县| 铜山县| 上杭县| 阿拉善左旗| 翼城县| 喀喇| 闻喜县| 鹿泉市| 东乌| 汉川市| 文成县|