前言

大家好,我是林三心,用最艰深的话讲最难的知识点是我的座右铭,根底是进阶的前提是我的初心,明天跟大家来唠唠嗑,如果后端真的返回给前端10万条数据,咱们前端要怎么优雅地展现进去呢?(哈哈假如后端真的能传10万条数据到前端)

前置工作

先把前置工作给做好,前面能力进行测试

后端搭建

新建一个 server.js 文件,简略起个服务,并返回给前端 10w 条数据,并通过 nodemon server.js 开启服务

没有装置 nodemon 的同学能够先全局装置 npm i nodemon -g
// server.jsconst http = require('http')const port = 8000;http.createServer(function (req, res) {  // 开启Cors  res.writeHead(200, {    //设置容许跨域的域名,也可设置*容许所有域名    'Access-Control-Allow-Origin': '*',    //跨域容许的申请办法,也可设置*容许所有办法    "Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",    //容许的header类型    'Access-Control-Allow-Headers': 'Content-Type'  })  let list = []  let num = 0  // 生成10万条数据的list  for (let i = 0; i < 1000000; i++) {    num++    list.push({      src: 'https://p3-passport.byteacctimg.com/img/user-avatar/d71c38d1682c543b33f8d716b3b734ca~300x300.image',      text: `我是${num}号嘉宾林三心`,      tid: num    })  }  res.end(JSON.stringify(list));}).listen(port, function () {  console.log('server is listening on port ' + port);})

前端页面

先新建一个 index.html

// index.html// 款式<style>    * {      padding: 0;      margin: 0;    }    #container {      height: 100vh;      overflow: auto;    }    .sunshine {      display: flex;      padding: 10px;    }    img {      width: 150px;      height: 150px;    }  </style>// html局部<body>  <div id="container">  </div>  <script src="./index.js"></script></body>

而后新建一个 index.js 文件,封装一个 AJAX 函数,用来申请这 10w 条数据

// index.js// 申请函数const getList = () => {    return new Promise((resolve, reject) => {        //步骤一:创立异步对象        var ajax = new XMLHttpRequest();        //步骤二:设置申请的url参数,参数一是申请的类型,参数二是申请的url,能够带参数        ajax.open('get', 'http://127.0.0.1:8000');        //步骤三:发送申请        ajax.send();        //步骤四:注册事件 onreadystatechange 状态扭转就会调用        ajax.onreadystatechange = function () {            if (ajax.readyState == 4 && ajax.status == 200) {                //步骤五 如果可能进到这个判断 阐明 数据 完满的回来了,并且申请的页面是存在的                resolve(JSON.parse(ajax.responseText))            }        }    })}// 获取container对象const container = document.getElementById('container')

间接渲染

最间接的形式就是间接渲染进去,然而这样的做法必定是不可取的,因为一次性渲染出 10w 个节点,是十分耗时间的,咱们能够来看一下耗时,差不多要耗费 12秒 ,十分耗费工夫

const renderList = async () => {    console.time('列表工夫')    const list = await getList()    list.forEach(item => {        const div = document.createElement('div')        div.className = 'sunshine'        div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`        container.appendChild(div)    })    console.timeEnd('列表工夫')}renderList()

setTimeout分页渲染

这个办法就是,把 10w 依照每页数量 limit 分成总共 Math.ceil(total / limit) 页,而后利用 setTimeout ,每次渲染1页数据,这样的话,渲染出首页数据的工夫大大缩减了

const renderList = async () => {    console.time('列表工夫')    const list = await getList()    console.log(list)    const total = list.length    const page = 0    const limit = 200    const totalPage = Math.ceil(total / limit)    const render = (page) => {        if (page >= totalPage) return        setTimeout(() => {            for (let i = page * limit; i < page * limit + limit; i++) {                const item = list[i]                const div = document.createElement('div')                div.className = 'sunshine'                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`                container.appendChild(div)            }            render(page + 1)        }, 0)    }    render(page)    console.timeEnd('列表工夫')}

requestAnimationFrame

应用 requestAnimationFrame 代替 setTimeout ,缩小了 重排 的次数,极大进步了性能,倡议大家在渲染方面多应用 requestAnimationFrame

const renderList = async () => {    console.time('列表工夫')    const list = await getList()    console.log(list)    const total = list.length    const page = 0    const limit = 200    const totalPage = Math.ceil(total / limit)    const render = (page) => {        if (page >= totalPage) return        // 应用requestAnimationFrame代替setTimeout        requestAnimationFrame(() => {            for (let i = page * limit; i < page * limit + limit; i++) {                const item = list[i]                const div = document.createElement('div')                div.className = 'sunshine'                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`                container.appendChild(div)            }            render(page + 1)        }, 0)    }    render(page)    console.timeEnd('列表工夫')}

文档碎片 + requestAnimationFrame

文档碎片 的益处

  • 1、之前都是每次创立一个 div 标签就 appendChild 一次,然而有了 文档碎片 能够先把1页的 div 标签先放进 文档碎片 中,而后一次性 appendChild container 中,这样缩小了 appendChild 的次数,极大进步了性能
  • 2、页面只会渲染 文档碎片 包裹着的元素,而不会渲染 文档碎片
const renderList = async () => {    console.time('列表工夫')    const list = await getList()    console.log(list)    const total = list.length    const page = 0    const limit = 200    const totalPage = Math.ceil(total / limit)    const render = (page) => {        if (page >= totalPage) return        requestAnimationFrame(() => {            // 创立一个文档碎片            const fragment = document.createDocumentFragment()            for (let i = page * limit; i < page * limit + limit; i++) {                const item = list[i]                const div = document.createElement('div')                div.className = 'sunshine'                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`                // 先塞进文档碎片                fragment.appendChild(div)            }            // 一次性appendChild            container.appendChild(fragment)            render(page + 1)        }, 0)    }    render(page)    console.timeEnd('列表工夫')}

懒加载

为了比拟艰深的解说,咱们启动一个 vue 前端我的项目,后端服务还是开着

其实实现原理很简略,咱们通过一张图来展现,就是在列表尾部放一个空节点 blank ,而后先渲染第1页数据,向上滚动,等到 blank 呈现在视图中,就阐明到底了,这时候再加载第二页,往后以此类推。

至于怎么判断 blank 呈现在视图上,能够应用 getBoundingClientRect 办法获取 top 属性

<script setup lang="ts">import { onMounted, ref, computed } from 'vue'const getList = () => {  // 跟下面一样的代码}const container = ref<HTMLElement>() // container节点const blank = ref<HTMLElement>() // blank节点const list = ref<any>([]) // 列表const page = ref(1) // 当前页数const limit = 200 // 一页展现// 最大页数const maxPage = computed(() => Math.ceil(list.value.length / limit))// 实在展现的列表const showList = computed(() => list.value.slice(0, page.value * limit))const handleScroll = () => {  // 当前页数与最大页数的比拟  if (page.value > maxPage.value) return  const clientHeight = container.value?.clientHeight  const blankTop = blank.value?.getBoundingClientRect().top  if (clientHeight === blankTop) {    // blank呈现在视图,则当前页数加1    page.value++  }}onMounted(async () => {  const res = await getList()  list.value = res})</script><template>  <div class="container" @scroll="handleScroll" ref="container">    <div class="sunshine" v-for="(item) in showList" :key="item.tid">      <img :src="item.src" />      <span>{{ item.text }}</span>    </div>    <div ref="blank"></div>  </div></template>

虚构列表

虚构列表须要解说的比拟多,在这里我分享一下我的一篇 虚构列表 的文章,哈哈我自认为讲的不错吧哈哈哈哈哈哈

联合“康熙选秀”,给大家讲讲“虚构列表”

结语

如果你感觉此文对你有一丁点帮忙,点个赞,激励一下林三心哈哈。或者能够退出我的摸鱼群 想进学习群,摸鱼群,请点击这里摸鱼,我会定时直播模仿面试,简历领导,答疑解惑