Key points: No lag, smooth interaction

First, the most traditional, the most simple and crude way

<! 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> How to render 10000 data on dom nodes </title> </head> <body> <ul id="root">

    </div>
    <script>
        function createOneHundredThousandData() {let arr = [];
            for(leti=0; i<100000; i++){ arr.push({ imgUrl:'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
                key:i
              })
            }
            return arr;
        }
        var beginTime = performance.now();
        console.log('beginTime',beginTime);
        let h = [];
        letData = createOneHundredThousandData () / / write 1 native jsforcyclefor(leti =0; i<data.length; i++){ h.push('<li>' + '<img src="'+ data[i].imgUrl +'" \ / >'+ 'current index ' + data[i].key + '<\/li>'); // h = data.map((item,index)=>'<li>' + '<img src="'+ item.imgUrl +'" \ / >'+ 'current index ' + item.key + '<\/li>');
        document.getElementById('root').innerHTML = h.join(' ');
        document.addEventListener('DOMContentLoaded'.function(){
          var endTime = performance.now();
          console.log('DOMContentLoaded endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('DOMContentLoaded render 100000 items takes ' + total + 's');
        });
        window.onload = function(){
          var endTime = performance.now();
          console.log('window.onload endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('window.onload render 100000 items takes ' + total + 's');  
        }
    </script>
</body>
</html>
Copy the code

The console running result of Chrome (official version 74.0.3729.169 (64-bit)) is as follows

BeginTime 398.8050000043586 DOMContentLoaded endTime 9032.814999984112 DOMContentLoaded Render 100000 items takes Window. onload endTime 17766.104999987874 window.onload render 100000 items takes 17.36730 secondsCopy the code

In other words, the render contains 100,000 records, and each piece of data is just a simple combination of images and text, which takes approximately 17 seconds. By the time the page is rendered, the user will probably be impatient and close the page. This is a newer version of Chrome. With other browsers, it might be even worse. Obviously, the traditional way is not qualified.

There are a few things I can add to the above demo:

  • Insert dom with innerHtml instead of document.createElement and Document. appendChild

  • 2, cache DOM string with array [], first push in, finally jion(” “), concatenate each item in the array into a string, than a string concatenate performance is much better

  • 3, Loop an array object, can use for loop, map, forEach, etc., when the amount of data is small, there is little difference between the two. In this case, we can see that map loop 100,000 data time performance is slightly inferior to ordinary for loop.

  • 4, Insert DOM nodes, you can also use clone technology, document fragment createDocumentFragment, the fundamental purpose is to minimize the number of DOM operations, so as to minimize the performance impact of redrawing and rearrangement. For those interested in digging deeper, check out High Performance JavaScript (owl head cover).

  • 5. The comparison between DOMContentLoaded event and window.onload event is also a key point in the page rendering process. To put it simply, DOMContentLoaded means that the dom is finished. In layman’s terms, the DOM tag is finished. It doesn’t matter what resources the DOM tag references or whether the request is loaded. In this example, DOMContentLoaded is triggered when the img tag is loaded without having to wait until all the resources pointed to by the IMG SRC are loaded. The window.onload event is not triggered until all SRC resources have been loaded.

DOMContentLoaded is also used in critical path optimization (first-screen operation optimization), where the page DOM loads and provides some interaction for the user. You can’t let the user see the UI without doing anything interactive.

SetTimeout to solve the stuck problem

Stalling, for the most part, is better than a significant delay in the user initiating an action, going to the page to respond to that action, and reporting the UI results back to the user. The user experience is not smooth.

From the perspective of JavaScript, it’s single-threaded, so it’s bound to only be able to handle one task at a time, and then the next task can only be handled after any of that processing is complete, so you can think of it as serial execution. (Learn more about JavaScript Event Loop for more details.) So, when the page is rendering, or it’s a time-consuming JavaScript operation, and the operation is not complete, and you initiate an interaction on the page, you don’t get a prompt response.

Back to the same problem, we’ll use slices when rendering 100,000 bytes of data (similar to the React Fiber idea). How do you understand that? This batch of tasks must be placed in an asynchronous callback (not the first batch), so that in subsequent renderings, priority is ceded to the execution queue thread, and when the execution queue is idle, it can come back and continue to fetch slices from the asynchronous callback to execute

<! 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> How to render 10000 data on dom nodes </title> </head> <body> <ul id="root">

    </div>
    <script>
        function createOneHundredThousandData() {let arr = [];
            for(leti=0; i<100000; i++){ arr.push({ imgUrl:'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
                key:i
              })
            }
            return arr;
        }
        var beginTime = performance.now();
        console.log('beginTime',beginTime);
        let h = [];
        letdata = createOneHundredThousandData(); // Render 100 data firstletFirstScreenData = data. The splice (0100); // Use the array splice method to intercept and modify the original arrayfor(leti=0; i<100; i++){let li = document.createElement('li');
          let img = document.createElement('img');
          img.src  = firstScreenData[i].imgUrl;
          li.appendChild(img);
          let text = document.createTextNode(firstScreenData[i].key);
          // console.log('partialData[i].key',partialData[i].key);
          li.appendChild(text);
          document.getElementById('root').appendChild(li); } / /setCallbacks in Timeout are executed when the main thread is idlesetTimeout(()=>{
          function renderHundred(n){
            // console.log('n=',n); // Render 100 at a timeletPartialData = data. The splice (0100);for(leti=0; i<100 && partialData.length>0; i++){let li = document.createElement('li');
              let img = document.createElement('img');
              img.src  = partialData[i].imgUrl;
              li.appendChild(img);
              let text = document.createTextNode(partialData[i].key);
              // console.log('partialData[i].key',partialData[i].key);
              li.appendChild(text);
              document.getElementById('root').appendChild(li);
            }            
            if(n){
              setTimeout(()=>{ renderHundred(n-1); },50) } } renderHundred(999); // Render data except for the first screen},1000); document.addEventListener('DOMContentLoaded'.function(){
          var endTime = performance.now();
          console.log('DOMContentLoaded endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('DOMContentLoaded render 100000 items takes ' + total + 's');
        });
        window.onload = function(){
          var endTime = performance.now();
          console.log('window.onload endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('window.onload render 100000 items takes ' + total + 's');  
        }
    </script>
</body>
</html>
Copy the code

The running results are as follows:

BeginTime 139.08000002265908 DOMContentLoaded endTime 193.2200000155717 DOMContentLoaded Render 100000 items takes Window. onload endTime 207.63000001898035 window.onload render 100000 items takes 0.06855 secondsCopy the code

There is no information in this data, which can be understood as the time-consuming statistics of the first slice (there are still 999 slices left), but the experience in interaction is greatly improved. You can view the performance data support for the Chrome Console. If you’re interested, copy this code and try it out. The conclusion is more optimistic.

Are there any other options besides setTimeout?

The answer is, yes. That is requestAnimationFrame

Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation. This method takes as an argument a callback function that is executed before the browser’s next redraw

Note: if you want to in the browser to update until the next redraw the next frame animation, then the callback function itself must once again call window. RequestAnimationFrame ()

As you can see from the official documentation, the keywords are: “Pass a callback function.” So can we use this instead of setTimeout?

Here is a snippet of code:

let data = createOneHundredThousandData();
let count = 0;
let totalLoop = 1000;// 渲染1000
function animatonCb(){
  console.log(count);
  letPartialData = data. The splice (0100); // Use the array splice method to intercept and modify the original arrayfor(leti=0; i<100 && partialData.length >=1; i++){let li = document.createElement('li');
    let img = document.createElement('img');
    img.src  = partialData[i].imgUrl;
    li.appendChild(img);
    let text = document.createTextNode(partialData[i].key);
    // console.log('partialData[i].key',partialData[i].key);
    li.appendChild(text);
    document.getElementById('root').appendChild(li);
  }
  if(count < totalLoop){
    count ++;
    requestAnimationFrame(animatonCb)
  }
}
requestAnimationFrame(animatonCb);
Copy the code

Look at the console data:

BeginTime 249.32000000262633 0 DOMContentLoaded endTime 279.33499999926426 DOMContentLoaded Render 100000 items takes Onload endTime 308.28500000643544 window.onload render 100000 items takes 0.05897 secsCopy the code

If we print the count loop, we see that this is interwoven between the DOMContentLoaded and onload events. Interested kids can take a closer look at requestAnimationFrame.

Anyway, the requestAnimationFrame will also fulfill our requirements. It’s a little bit better than setTimeout.

Iv. What about 100,000 pieces of data after loading?

The above two schemes, that is, to solve the problem of how not to render. In this example, the DOM structure of each record is not complex and may look fine. But the actual business scenario is certainly more complex than that. Every time you modify the DOM, you cause 100,000 pieces of data to be rearranged, which is definitely a performance problem.

The solution is to monitor whether the element is IntersectionObserver in a visible window

The IntersectionObserver interface (which is part of the IntersectionObserver API) provides a way to asynchronously observe the intersecting status of a target element with its ancestor element or top-level document viewport. Ancestor elements and viewports are called roots. When a IntersectionObserver object is created, it is configured to listen on a given proportion of visible areas in the root. Once IntersectionObserver is created, its configuration cannot be changed, so a given observer object can only be used to monitor specific changes in the visible area. However, you can configure to listen on multiple target elements in the same observer object.

The general idea is as follows:

  • Set up the total data source, the page content data storage container
  • Make rules for the storage containers of page content and data (assume that the storage containers are set to 200, and a maximum of 20 containers are displayed on a screen. So the storage container can display 10 screens of data.
  • When the user slides to 6 screens of data, it is obvious that the first 5 screens of data are not visible in the window, then you can delete the first 3 screens of data in the storage container. At the same time, data from screen 11 to screen 13 are taken from the total data source.

I will study this in detail when I have time.

Five, the conclusion

Such problems are often asked by bat, which is a big factory. The knowledge point covers a wide range of points. After a good grasp, the front-end performance and caton’s understanding will be more thorough.