一站式做網(wǎng)站哪家專業(yè)網(wǎng)站怎么收錄到百度
當(dāng)瀏覽器遇到性能瓶頸導(dǎo)致頁面卡頓時(shí),你會(huì)怎么處理?如何查找問題的原因?
瀏覽器本身自帶性能檢測工具,通常我們分析由腳本導(dǎo)致的頁面卡頓會(huì)選擇 性能(performance)
選項(xiàng)卡,在其中我們可以找到導(dǎo)致頁面卡頓的函數(shù),另外通過觀察我們可以看到腳本的執(zhí)行時(shí)間占比以及頁面卡頓的程度。
此外還有 內(nèi)存
選項(xiàng)卡,可以截取當(dāng)前頁面的內(nèi)存快照,由于我們的頁面通常不像服務(wù)器那樣一直運(yùn)行,所以平常我們也不會(huì)過于關(guān)注頁面的內(nèi)存使用問題。
1. 渲染大量數(shù)據(jù)
由于要渲染大量數(shù)據(jù),我們剛開始學(xué)習(xí)前端時(shí),如果經(jīng)驗(yàn)不足就會(huì)簡單地將所有數(shù)據(jù)全部一次性地渲染到頁面上,這樣勢必會(huì)造成頁面的卡頓。所以這種方法我這里也不會(huì)去介紹。
下面直接進(jìn)入正軌,首先提出一個(gè)問題引發(fā)思考:如果讓你去實(shí)現(xiàn),你會(huì)怎么實(shí)現(xiàn)?
1.1. 采用異步
我首先是想到如果要渲染大量數(shù)據(jù),我們可以將這一個(gè)巨大的任務(wù)拆分成一個(gè)個(gè)的小任務(wù)來執(zhí)行,將這一個(gè)個(gè)的小任務(wù)加入到任務(wù)隊(duì)列當(dāng)中采用異步的方式來慢慢地執(zhí)行。
不過這個(gè)會(huì)有個(gè)缺陷,由于這么多數(shù)據(jù)是按照順序來依次執(zhí)行的,所以當(dāng)用戶想查看比較靠后的數(shù)據(jù)時(shí),用戶會(huì)發(fā)現(xiàn)數(shù)據(jù)一直在加載中、一直在渲染,然后用戶等啊等,最后點(diǎn)擊關(guān)閉標(biāo)簽頁。
下面是一個(gè)簡單的使用異步實(shí)現(xiàn)的邏輯(只包含部分代碼):
const ul = document.querySelector('ul');let total = 100_000_000;
let count = 0;function loop() {const fragment = document.createDocumentFragment();for (let i = 0; i < 20 && count < total; i++, count++) {const li = document.createElement("li");li.textContent = count;fragment.appendChild(li);}ul.appendChild(fragment);window.requestAnimationFrame(loop);
}loop();
觀察上面的代碼,其中使用到了 requestAnimationFrame
函數(shù),它接收一個(gè)函數(shù)作為參數(shù),這個(gè)函數(shù)會(huì)在瀏覽器每一幀渲染結(jié)束之后執(zhí)行。
為什么不使用 setTimeout
定時(shí)器,因?yàn)?setTimeout 函數(shù)無法控制函數(shù)的執(zhí)行時(shí)機(jī),我們只知道函數(shù)會(huì)被加入到任務(wù)隊(duì)列,但并不知道函數(shù)會(huì)在何時(shí)執(zhí)行,而 requestAnimationFrame 函數(shù)則固定在每一幀渲染之后執(zhí)行,這樣就不會(huì)在視覺上讓用戶感覺頁面卡頓。
當(dāng)然如果 requestAnimationFrame 的函數(shù)執(zhí)行時(shí)間過長,會(huì)推遲下一幀的渲染,所以盡量不要將耗時(shí)任務(wù)放在其中。
1.2. 虛擬列表
虛擬列表采用的思想類似于懶加載,都是只加載用戶看得見的數(shù)據(jù),懶加載在計(jì)算機(jī)上隨處可見,比如單例模式中的餓漢式、圖片的懶加載等,在我們常用的 QQ 中,像消息列表,聯(lián)系人列表他也采用了類似于虛擬列表的形式進(jìn)行渲染,以減少內(nèi)存的使用量。
因?yàn)檫@里我們要處理大量數(shù)據(jù)的渲染,如果要求所有的數(shù)據(jù)必須全部渲染在可視區(qū)當(dāng)中,那么懶加載就派不上用場了。
虛擬列表主要由可視區(qū)的數(shù)據(jù)構(gòu)成,其他的位置都是空白。我們可以通過 padding 或者是 top/left 來制造空白。如下圖所示(圖片來自這篇文章 面試官:如何一次性渲染十萬條數(shù)據(jù) - 掘金 (juejin.cn)),可視區(qū)展示我們想看見的數(shù)據(jù),緩存區(qū)就是我說的空白。
實(shí)現(xiàn)的代碼如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>* {margin: 0;padding: 0;}.scroll-box {height: 100vh;overflow-y: scroll;}</style>
</head>
<body><div class="scroll-box"><ul></ul></div><script src="./test.js"></script>
</body>
</html>
對應(yīng)的 test.js:
const ul = document.querySelector('ul');
const scrollBox = document.querySelector('.scroll-box');let total = 100_000_000;
let count = 0;
let liHeight = 20;
// 要展示的數(shù)據(jù)量
let showCount = scrollBox.clientHeight / liHeight >> 0;let totalHeight = total * liHeight;function generateLi() {// 計(jì)算出來的處于可視區(qū)頂部的數(shù)據(jù)索引let index = (scrollBox.scrollTop / liHeight) >> 0;const fragment = document.createDocumentFragment();// 制造空白ul.style.paddingTop = `${index * liHeight}px`;ul.style.paddingBottom = `${(totalHeight - (index + 20) * liHeight)}px`;// 生成可視區(qū)數(shù)據(jù)for (let i = 0; i < showCount; i++) {const li = document.createElement("li");li.textContent = `${index + i}`;fragment.appendChild(li);}// 重新設(shè)置元素,這里元素并沒有考慮復(fù)用ul.innerHTML = "";ul.appendChild(fragment);
}generateLi();scrollBox.addEventListener("scroll", generateLi);
多嘴一句,當(dāng)前元素可以滾動(dòng)才有 scrollTop 值,否則一直為 0。比如當(dāng)前元素固定了高度,但是它的子元素的高度超出了它的高度,就會(huì)導(dǎo)致溢出,這是我們可以設(shè)置 overflow-y: auto
,當(dāng)前元素就會(huì)有滾動(dòng)條。
而 scrollTop 就是當(dāng)前元素的頂部與它的子元素的頂部的距離,沒有負(fù)值。
2. 參考
參考文獻(xiàn):
- 面試官:如何一次性渲染十萬條數(shù)據(jù) - 掘金 (juejin.cn)