• 作者:老汪软件技巧
  • 发表时间:2024-09-12 10:01
  • 浏览量:

前言

在面试中,有一种题目让我又爱又恨,爱它的奇思妙想,恨自己的无能为力.它就是场景题目.它考察我们的编程能力,以及临场发挥能力等等.那么在本文中作者将介绍场景题中的,如何一次性渲染十万条数据?

正文

暴力渲染

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    <ul id="container">
    ul>
    <script>
        let ul = document.getElementById("container");
        const total = 100000
        let now = Date.now();
        for(let i = 0; i < total; i++){
            let li = document.createElement("li")
            li.innerHTML = ~~(Math.random() * total)
            ul.appendChild(li)
        }
        console.log('JS运行耗时',Date.now() - now);
        setTimeout(()=>{
            console.log('页面加载总时长:',Date.now() - now);
            
        })
    script>
body>
html>

在上面这个暴力渲染是我们能想到的最简单的实现方法,但是毕竟是最简单的方法,所以会存在各种问题.我们先定义了一个变量now以及打印这个Date.now() - now这个时间表示的是我们V8引擎执行完同步代码的时间.这时我们就不得不来聊一个细节上的问题.页面渲染这一步骤在事件循环的执行的时间.首先我们知道的是在事件循环机制中,V8引擎会先执行同步代码,再执行微任务队列中的微任务,在执行完微任务之后,会检查需不需要进行页面渲染这一操作,如果有就会先执行页面渲染这一操作,然后再去执行下一个宏任务.那么我们再去看代码中的setTimeout()这一个宏任务中计算出来的时间就是页面渲染完毕的时间.我们来看看打的时间

这里我们可以很清楚的看到V8引擎的执行并没有花费太长的时间,而是页面渲染花的时间太长了.所以我们需要优化页面渲染这一个过程.

时间分片之分批渲染

分批渲染的理论依据是:假设我们又十万条数据,但是我们每一次只渲染20条数据,渲染5000次.那么此时大家就会很疑惑渲染5000次?听起来不是更麻烦嘛?但是事实上这个方案会好一点,我们的V8引擎执行5000次的时间开销其实不多,但是我们让页面页面每一次只渲染20条数据这一过程会让页面将数据渲染完比一次性渲染十万条数据更加流畅.

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    <ul id="container">
    ul>
    <script>
        let ul = document.getElementById("container");
        const total = 100000//总数据
        let once = 20//一次最多渲染的数据
        let page = total / once//一共5000页
        let index = 0;//记录下标,防止数据丢失
        function loop(curTotal,curIndex){
            let pageCount = Math.min(once,curTotal)
            setTimeout(()=>{
                for(let i = 0; i < pageCount; i++){
                    let li = document.createElement("li")
                    li.innerHTML =curIndex + i +':'+ ~~(Math.random() * total)
                    ul.appendChild(li)
                }
                loop(curTotal-pageCount,curIndex+pageCount)
            })
        }
        loop(total,index);
    script>
body>
html>

这里一段的setTimeout()定时器我们想要理解其实也简单,我们严格按照事件循环机制的执行顺序来.但是我们先来看这一句代码let pageCount = Math.min(once,curTotal)它是保证我们渲染的数据条数都合理,如果我们不写的话.我们来看这一种情况假设我们需要渲染的数据不是整数条而是十万零一条.那么最后一条数据如何处理.如果没有这一个判断的话,代码会按部就班的渲染20条数据,总数据就会变成十万零二十条数据,跟我们实际要求不一样.所以我们必须判断每一次需要渲染多少条.然后我们来看处理数据的事件循环过程.先执行完同步代码let pageCount = Math.min(once,curTotal),然后遇到宏任务定时器,定时器中的同步代码会执行,这是存在页面渲染,然后递归loop(curTotal-pageCount,curIndex+pageCount)递归也会碰到宏任务,但是会先执行上一个宏任务,由于需要进行页面渲染,所以会先执行页面渲染在执行下一个宏任务,这样循环往复.就可以完成5000次渲染.

这就是对暴力解法的优化,然后我们来考虑一个问题.定时器的等待时间一定是精准无误的嘛?事实上不是精准的存在误差,然后我们电脑屏幕时每1/60秒刷新一次.如果执行完一次页面渲染时间和屏幕自己的刷新时间不一致,就会出现问题库,所以我们根据这一点就必须优化.

优化页面渲染时间问题

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    <ul id="container">
    ul>
    <script>
        let ul = document.getElementById("container");
        const total = 100000//总数据
        let once = 20//一次最多渲染的数据
        let page = total / once//一共5000页
        let index = 0;//记录下标,防止数据丢失
        
        function loop(curTotal,curIndex){
            let pageCount = Math.min(once,curTotal)
            requestAnimationFrame(()=>{
                for(let i = 0; i < pageCount; i++){
                    let li = document.createElement("li")
                    li.innerHTML =curIndex + i +':'+ ~~(Math.random() * total)
                    ul.appendChild(li)
                }
                loop(curTotal-pageCount,curIndex+pageCount)
            })
        }
        loop(total,index);
    script>
body>
html>

优化这一点非常简单,我们只需要将定时器换成requestAnimationFrame(),这一个定时器的时间固定与屏幕的刷新时间一致,其他的方面和setTimeout()没有区别.

渲染队列优化-文档碎片化

这个优化是假设我们不知道浏览器的渲染队列可以存多少条数据,所以我们以防存在爆栈,我们也需要优化回流重绘这一过程的问题.这里我们可以采用文档碎片化.

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    <ul id="container">
    ul>
    <script>
        let ul = document.getElementById("container");
        const total = 100000//总数据
        let once = 20//一次最多渲染的数据
        let page = total / once//一共5000页
        let index = 0;//记录下标,防止数据丢失
        
        
        function loop(curTotal,curIndex){
            let pageCount = Math.min(once,curTotal)
            requestAnimationFrame(()=>{
                let fragment = document.createDocumentFragment();
                for(let i = 0; i < pageCount; i++){
                    let li = document.createElement("li")
                    li.innerHTML =curIndex + i +':'+ ~~(Math.random() * total)
                    // ul.appendChild(li)
                    fragment.appendChild(li)
                }
                ul.appendChild(fragment)
                loop(curTotal-pageCount,curIndex+pageCount)
            })
        }
        loop(total,index);
    script>
body>
html>

let fragment = document.createDocumentFragment();直接创建一个文档碎片,这个标签是不会存在在文档流的,我们只需要先将20条数据放在文档碎片中,当20条数据都插入完成时,再将这个文档碎片插入到ul中,这样我们就不用一条一条的数据插入到ul中了.

上述就是三种比较常规的方法了,希望对各位有所帮助