【面试题】 给你十万条数据,怎么样顺滑的渲染出来?

简介: 【面试题】 给你十万条数据,怎么样顺滑的渲染出来?

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

前言

这是一道面试题,这个问题出来的一刹那,很容易想到的就是for循环100000次吧,但是这方案着实让浏览器崩溃啊!还有什么解决方案呢?

正文

1. for 循环100000次

虽说for循环有点low,但是,当面试官问,为什么会让浏览器崩溃的时候,你知道咋解释吗?

来个例子吧,我们需要在一个容器(ul)中存放100000项数据(li):

我们的思路是打印js运行时间页面渲染时间,第一个console.log的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间;第二个console.log是在 setTimeout 中的,它的触发时间是在渲染完成,在下一次Event Loop中执行的。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <ul id="ul"></ul>
  <script>
    let now = Date.now();  //Date.now()得到时间戳
    const total = 100000
    const ul = document.getElementById('ul')
    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);
    },0)
    console.log();
  </script>
</body>
</html>

运行可以看到这个数据:

这渲染开销也太大了吧!而且它是十万条数据一起加载出来,没加载完成我们看到的会是一直白屏;在我们向下滑动过程中,页面也会有卡顿白屏现象,这就需要新的方案了。继续看!

2. 定时器

我们可以使用定时器实现分页渲染,我们继续拿上面那份代码进行优化:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <ul id="ul"></ul>
  <script>
    let now = Date.now();  //Date.now()得到时间戳
    const total = 100000  //总共100000条数据
    const once = 20  //每次插入20条
    const page = total / once  //总页数
    let index = 1
    const ul = document.getElementById('ul')
    function loop(curTotal, curIndex) {
      if (curTotal <= 0) {  判断总数居条数是否小于等于0
        return false
      }
      let pageCount = Math.min(curTotal, once)  //以便除不尽有余数
      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)
      }, 0)
    }
    loop(total, index)
  </script>
</body>
</html>

运行后可以看到这十万条数据并不是一次性全部加载出来,浏览器右方的下拉条有顺滑的效果哦,如下图:

但是当我们快速滚动时,页面还是会有白屏现象,如下图所示,这是为什么呢?

可以说有两点原因:

  • 一是setTimeout的执行时间是不确定的,它属于宏任务,需要等同步代码以及微任务执行完后执行。
  • 二是屏幕刷新频率受分辨率和屏幕尺寸影响,而setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕刷新时间相同。

3. requestAnimationFrame

我们这次采用requestAnimationFrame的方法,它是一个用于在下一次浏览器重绘之前调用指定函数的方法,它是 HTML5 提供的 API。

我们插入一个小知识点, requestAnimationFrame 和 setTimeout 的区别:

· requestAnimationFrame的调用频率通常为每秒60次。这意味着我们可以在每次重绘之前更新动画的状态,并确保动画流畅运行,而不会对浏览器的性能造成影响。

· setIntervalsetTimeout它可以让我们在指定的时间间隔内重复执行一个操作,不考虑浏览器的重绘,而是按照指定的时间间隔执行回调函数,可能会被延迟执行,从而影响动画的流畅度。

还有一个问题,我们多次创建li挂到ul上,这样会导致回流,所以我们用虚拟文档片段的方式去优化它,因为它不会触发DOM树的重新渲染!

<!DOCTYPE html>
<html lang="en">
![rf.gif](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3eab42b37f53408b981411ee54088d5a~tplv-k3u1fbpfcp-watermark.image?)
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
![st.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e922cc57a044f5e9e48e58bda5f6756~tplv-k3u1fbpfcp-watermark.image?)
<body>
  <ul id="ul"></ul>
  <script>
    let now = Date.now();  //Date.now()得到时间戳
    const total = 10000
    const once = 20
    const page = total / once
    let index = 1
    const ul = document.getElementById('ul')
    function loop(curTotal, curIndex) {
      if (curTotal <= 0) {
        return false
      }
      let pageCount = Math.min(curTotal, once)  //以便除不尽有余数
      requestAnimationFrame(()=>{
        let fragment = document.createDocumentFragment() //虚拟文档
        for (let i = 0; i < pageCount; i++) {
          let li = document.createElement('li')
          li.innerHTML = curIndex + i + ':' + ~~(Math.random() * total)  
          fragment.appendChild(li)
        }
        ul.appendChild(fragment)
        loop(curTotal - pageCount, curIndex + pageCount)
      })
    }
    loop(total, index)
  </script>
</body>
</html>

可以看到它白屏时间没有那么长了:

还有没有更好的方案呢?当然有!往下看!

4. 虚拟列表

我们可以通过这张图来表示虚拟列表红框代表你的手机黑条代表一条条数据

思路:我们只要知道手机屏幕最多能放下几条数据,当下拉滑动时,通过双指针的方式截取相应的数据就可以了。

🚩 PS:为了防止滑动过快导致的白屏现象,我们可以使用预加载的方式多加载一些数据出来。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <title>虚拟列表</title>
  <style>
    .v-scroll {
      height: 600px;
      width: 400px;
      border: 3px solid #000;
      overflow: auto;
      position: relative;
      -webkit-overflow-scrolling: touch;
    }
    .infinite-list {
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      z-index: -1;
    }
    .scroll-list {
      left: 0;
      right: 0;
      top: 0;
      position: absolute;
      text-align: center;
    }
    .scroll-item {
      padding: 10px;
      color: #555;
      box-sizing: border-box;
      border-bottom: 1px solid #999;
    }
  </style>
</head>
<body>
  <div id="app">
    <div ref="list" class="v-scroll" @scroll="scrollEvent($event)">
      <div class="infinite-list" :style="{ height: listHeight + 'px' }"></div>
      <div class="scroll-list" :style="{ transform: getTransform }">
        <div ref="items" class="scroll-item" v-for="item in visibleData" :key="item.id"
          :style="{ height: itemHeight + 'px',lineHeight: itemHeight + 'px' }">{{ item.msg }}</div>
      </div>
    </div>
  </div>
  <script>
    var throttle = (func, delay) => {  //节流
      var prev = Date.now();
      return function () {
        var context = this;
        var args = arguments;
        var now = Date.now();
        if (now - prev >= delay) {
          func.apply(context, args);
          prev = Date.now();
        }
      }
    }
    let listData = []
    for (let i = 1; i <= 10000; i++) {
      listData.push({
        id: i,
        msg: i + ':' + Math.floor(Math.random() * 10000)
      })
    }
    const { createApp } = Vue
    createApp({
      data() {
        return {
          listData: listData,
          itemHeight: 60,
          //可视区域高度
          screenHeight: 600,
          //偏移量
          startOffset: 0,
          //起始索引
          start: 0,
          //结束索引
          end: null,
        };
      },
      computed: {
        //列表总高度
        listHeight() {
          return this.listData.length * this.itemHeight;
        },
        //可显示的列表项数
        visibleCount() {
          return Math.ceil(this.screenHeight / this.itemHeight)
        },
        //偏移量对应的style
        getTransform() {
          return `translate3d(0,${this.startOffset}px,0)`;
        },
        //获取真实显示列表数据
        visibleData() {
          return this.listData.slice(this.start, Math.min(this.end, this.listData.length));
        }
      },
      mounted() {
        this.start = 0;
        this.end = this.start + this.visibleCount;
      },
      methods: {
        scrollEvent() {
          //当前滚动位置
          let scrollTop = this.$refs.list.scrollTop;
          //此时的开始索引
          this.start = Math.floor(scrollTop / this.itemHeight);
          //此时的结束索引
          this.end = this.start + this.visibleCount;
          //此时的偏移量
          this.startOffset = scrollTop - (scrollTop % this.itemHeight);
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

可以看到白屏现象解决了!

结语

解决十万条数据渲染的方案基本都在这儿了,还有更好的方案等待大佬输出!

前端面试题库 (面试必备)            推荐:★★★★★

地址:https://mp.weixin.qq.com/s/GWWBm99rjWBcsgIGXyBwig

相关文章
|
6月前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
465 2
|
6月前
|
SQL 缓存 easyexcel
面试官问10W 行级别数据的 Excel 导入如何10秒处理
面试官问10W 行级别数据的 Excel 导入如何10秒处理
267 0
|
6月前
|
SQL 前端开发 程序员
【面试题】前端开发中如何高效渲染大数据量?
【面试题】前端开发中如何高效渲染大数据量?
121 0
|
22天前
|
存储 缓存 关系型数据库
滴滴面试:单表可以存200亿数据吗?单表真的只能存2000W,为什么?
40岁老架构师尼恩在其读者交流群中分享了一系列关于InnoDB B+树索引的面试题及解答。这些问题包括B+树的高度、存储容量、千万级大表的优化、单表数据量限制等。尼恩详细解释了InnoDB的存储结构、B+树的磁盘文件格式、索引数据结构、磁盘I/O次数和耗时,以及Buffer Pool缓存机制对性能的影响。他还提供了实际操作步骤,帮助读者通过元数据找到B+树的高度。尼恩强调,通过系统化的学习和准备,可以大幅提升面试表现,实现“offer直提”。相关资料和PDF可在其公众号【技术自由圈】获取。
|
27天前
|
监控 Java easyexcel
面试官:POI大量数据读取内存溢出?如何解决?
【10月更文挑战第14天】 在处理大量数据时,使用Apache POI库读取Excel文件可能会导致内存溢出的问题。这是因为POI在读取Excel文件时,会将整个文档加载到内存中,如果文件过大,就会消耗大量内存。以下是一些解决这一问题的策略:
67 1
|
30天前
|
存储 关系型数据库 MySQL
面试官:MySQL一次到底插入多少条数据合适啊?
本文探讨了数据库插入操作的基础知识、批量插入的优势与挑战,以及如何确定合适的插入数据量。通过面试对话的形式,详细解析了单条插入与批量插入的区别,磁盘I/O、内存使用、事务大小和锁策略等关键因素。最后,结合MyBatis框架,提供了实际应用中的批量插入策略和优化建议。希望读者不仅能掌握技术细节,还能理解背后的原理,从而更好地优化数据库性能。
|
1月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
59 0
|
3月前
|
Java
【Java基础面试五】、 int类型的数据范围是多少?
这篇文章回答了Java中`int`类型数据的范围是-2^31到2^31-1,并提供了其他基本数据类型的内存占用和数值范围信息。
【Java基础面试五】、 int类型的数据范围是多少?
|
4月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
3月前
|
存储 负载均衡 算法
[go 面试] 一致性哈希:数据分片与负载均衡的黄金法则
[go 面试] 一致性哈希:数据分片与负载均衡的黄金法则