Preface
In our practical work, we often encounter the insertion of thousands or even tens of thousands of data into a page. Obviously, if we render tens of thousands of data at the same time, the page will definitely get stuck. At this time, we need to optimize the performance.
There are generally two ways to insert large amounts of data at one time:
1. Time slicing
2. Virtual List
This time I want to introduce the use of time slicing to optimize performance.
Now take inserting 10,000 pieces of data into a page as an example
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <ul id='contain'></ul> </body> </html>
A rough one-off rendering
Rape inserts 100,000 data let now = Date.now(); let Li = document.getElementById('contain') const totalPage = 10000 for(let i =0;i<totalPage;i++){ let li = document.createElement('li'); li.innerText = Math.floor(Math.random()*totalPage) Li.appendChild(li); } console.log(`js Running time; ${Date.now()-now}`) setTimeout(()=>{ console.log(`Total running time: ${Date.now()-now}`) },0)
Rough one-time rendering can make the page very cramped, and I recorded the running time of js and the completion time of page rendering.
The first console.log runs 200 ms for js and the second console.log runs 3200 MS for page rendering. So it can be seen that when rendering a large amount of data, the performance of js is still possible. Page carton is caused by page rendering stage.
Why the second time is when the page rendering is completed, which is about Event Loop of the js operating mechanism, which I will explain in detail in the next article.
Use timers
From the above we can see that the city that caused the page carton rendered a lot of DOM at the same time, so we can consider the rendering process in batches, we use timers for batches. This use of recursion, each time rendering 20 data, batch process
//setTimeout is used let Li = document.getElementById('contain') const totalPage = 10000 let once = 20 let index = 0 function loop(currentPage, currentIndex) { if (currentPage <= 0) { return false } let pageCount = Math.min(currentPage, once) setTimeout(()=>{ for (let i = 0; i < pageCount; i++) { let li = document.createElement('li'); li.innerText = Math.floor(Math.random() * totalPage) Li.appendChild(li); } loop(currentPage-pageCount, currentIndex+pageCount) // },0) } loop(totalPage, index)
Obviously, the speed of page loading has been very fast, every refresh can quickly see the full screen data, but when we scroll the page quickly, there will be a flash screen or white screen phenomenon. Obviously, we still need to optimize, so why is there a flash screen phenomenon?
Analysis of Flash Screen Phenomenon
The continuous pages we usually see are actually made up of static pages, each drawing being a frame. FPS represents the number of screen updates per second. Most computer monitors are 60 Hz per second. When you don't do anything, most monitors constantly update the images on the screen at a frequency of 60 times per second.
Why can't people feel it?
Because the eye has a visual retention effect, that is, the impression left in the brain by the previous painting has not disappeared, and the next painting has come again. Let us feel that the page is static. Therefore, most browsers limit the redrawing, mostly not exceeding the redrawing frequency of the display. Because even beyond that frequency, the user experience will not improve.
From this we can know that the flash screen appears on the page because the rendering speed is too slow each time. This is because setTimeout execution time is uncertain. In js, the setTimeout task is placed in the event queue, and only when the main thread has finished executing will it check whether the event queue has tasks to do. So setTimeout may actually be executed later than the set time. Therefore, this will lead to inconsistency between setTimeout's execution pace and screen refresh pace, resulting in frame loss and screen jamming.
Use the request Animation Frame
Compared with setTimeout, the biggest advantage of the request Animation Frame is that the system decides when the callback function will be executed. If the screen refresh rate is 60 hz, the callback function is executed every 16.7 ms; if the screen refresh rate is 80 hz, the callback function interval (1000/80 = 12.5 ms) is refreshed once, in other words, the request Animation Frame follows the system refresh step. It ensures that the callback function is executed only once at each refresh interval of the screen, so that no frame loss occurs.
let Li = document.getElementById('contain') const totalPage = 10000 let once = 20 let index = 0 function loop(currentPage, currentIndex) { if (currentPage <= 0) { return false } let pageCount = Math.min(currentPage, once) window.requestAnimationFrame(()=>{ for (let i = 0; i < pageCount; i++) { let li = document.createElement('li'); li.innerText = Math.floor(Math.random() * totalPage) Li.appendChild(li); } loop(currentPage-pageCount, currentIndex+pageCount) }) } loop(totalPage, index)
In this way, the loading speed of our pages is fast, and there is no flicker or frame loss when we scroll quickly.
Using Document Fragment
Don't assume that this optimization is complete, you can also use Document Fragment.
Document Fragments are DOM nodes, but they are not part of the DOM tree and exist in memory. When an append element is added to a document, the stylesheet is computed at the same time. When an append element is added to a document Fragment, the stylesheet of the element is not computed, so the document Fragment performance is better.
Finally, the code is modified as follows:
function loop(currentPage, currentIndex) { if (currentPage <= 0) { return false } let pageCount = Math.min(currentPage, once) window.requestAnimationFrame(()=>{ let fragment = document.createDocumentFragment() for (let i = 0; i < pageCount; i++) { let li = document.createElement('li'); li.innerText = Math.floor(Math.random() * totalPage) fragment.appendChild(li); } Li.appendChild(fragment) loop(currentPage-pageCount, currentIndex+pageCount) }) } loop(totalPage, index)