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

news2024/9/24 11:32:57

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

地址:前端面试题库

前言

这是一道面试题,这个问题出来的一刹那,很容易想到的就是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>

运行可以看到这个数据:

image.png


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

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. 虚拟列表

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

image.png

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

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

 

结语

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

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

地址:前端面试题库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/817679.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LoRa无线数据传输的温湿度监测预警系统解决方案

为了维护仓储物品的品质&#xff0c;创造适宜的存储环境&#xff0c;就需要实时监测环境的温湿度信息&#xff0c;一旦温湿度出现异常就需要及时调整控制&#xff0c;从而保证品质稳定也能避免损失。 物通博联提供了软硬件一体的工业物联网解决方案&#xff0c;基于温湿度监测…

2023年超越期待的高性能视频剪辑主机推荐| Intel 蝰蛇峡谷测评

1、开箱 蝰蛇峡谷的开箱体验是非常令人兴奋的。首先&#xff0c;打开包装后&#xff0c;你会看到一个精致且高质感的机箱&#xff0c;给人一种专业的感觉。蝰蛇峡谷的外观设计简洁大方&#xff0c;黑色的机箱与红色的Logo相得益彰&#xff0c;展现了其高性能的特点。 在打开机…

文件上传到远程服务器

文件上传 一、上传文件到本地 package com.ruoyi.system.knowledgebase;import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.system.domain.SzKnowledge; import com.ruoyi.system.service.ISzKnowledgeServi…

MyBatis缓存-提高检索效率的利器--二级缓存

文章目录 缓存-提高检索效率的利器缓存-官方文档二级缓存基本介绍二级缓存原理图 二级缓存快速入门快速入门注意事项和使用陷阱理解二级缓存策略的参数 四大策略如何禁用二级缓存mybatis 刷新二级缓存的设置 缓存-提高检索效率的利器 缓存-官方文档 文档地址: https://mybati…

低代码平台实际解决了哪些问题?

一、前言 目前低代码平台如火如荼。这一新兴技术为企业提供了一种高效、灵活、快速开发应用程序的方法&#xff0c;并在短时间内取得了巨大成功。然而&#xff0c;我们不得不面对低代码平台的优劣以及其所带来的挑战。本文将深入探讨低代码平台在不同情况下的优劣势&#xff0c…

找不到Windows SDK版本 10.0.18362.0.请安装所需版本的 Windows SDK,或者在项目属性页中或通过右键单击解决

找不到Windows SDK版本 10.0.18362.0.请安装所需版本的 Windows SDK,或者在项目属性页中或通过右键单击解决 安装相应的组件 项目——重定目标解决方案——然后选择版本

Electron逆向调试

复杂程序处理方式&#xff1a; 复杂方式通过 调用窗口 添加命令行参数 启动允许调用&#xff0c;就可以实现调试发布环境的electron程序。 断点调试分析程序的走向&#xff0c;程序基本上会有混淆代码处理&#xff0c; 需要调整代码格式&#xff0c;处理程序。

XGBoost的基础思想与实现

目录 1. XGBoost VS 梯度提升树 1.1 XGBoost实现精确性与复杂度之间的平衡 1.2 XGBoost极大程度地降低模型复杂度、提升模型运行效率 1.3 保留了部分与梯度提升树类似的属性 2. XGBoost回归的sklearnAPI实现 2.1 sklearn API 实现回归 2.2 sklearn API 实现分类 3. XGBo…

HCIP--云计算题库 V5.0版本

在国家政策的支持下&#xff0c;我国云计算应用市场发展明显加快&#xff0c;越来越多的企业开始介入云产业&#xff0c;出现了大量的应用解决方案&#xff0c;云应用的成功案例逐渐丰富&#xff0c;用户了解和认可程度不断提高&#xff0c;云计算产业发展迎来了“黄金机遇期”…

第一百一十九天学习记录:感谢CSDN对一个大龄程序员的鼓励

在经历了一百多天的学习之后&#xff0c;不仅感觉学习之路道阻且长&#xff0c;反而因为好多需要学习的东西而开始有点士气低迷&#xff0c;结果CSDN官方的一条私信再次鼓舞了我。 我在坚持平均每天写一篇学习记录。结果没想到居然能拿到CSDN活动的奖励。 这无疑是对我持续学习…

死锁产生的原因及解决方案

死锁 1. 死锁的成因2. 解决方案 1. 死锁的成因 互斥条件: 一个资源每次只能被一个进程使用。请求与保持条件&#xff1a;一个进程因请求资源而阻塞时&#xff0c;对已获得的资源保持不放。不可剥夺条件:进程已获得的资源&#xff0c;在末使用完之前&#xff0c;不能强行剥夺。…

详解FreeRTOS:FreeRTOS程序启动流程(基础篇—5)

裸机系统上电时第一个执行的是启动文件里由汇编编写的复位函数Reset_Handler,复位函数最后会调用 C 库函数__main,__main 函数的主要工作是初始化系统的堆和栈,最后调用 C 中的 main 函数。如下图所示: 1、创建任务 在 main()函数中,我们直接可以对 FreeRTOS 进行创建任务…

iOS开发-实现热门话题标签tag显示控件

iOS开发-实现热门话题标签tag显示控件 话题标签tag显示非常常见&#xff0c;如选择你的兴趣&#xff0c;选择关注的群&#xff0c;超话&#xff0c;话题等等。 一、效果图 二、实现代码 由于显示的是在列表中&#xff0c;这里整体控件是放在UITableViewCell中的。 2.1 标签…

使用vscode进行远程开发服务器配置

1.下载vscode 2.给vscode 安装python 和 remote ssh插件 remote—SSH扩展允许您使用任何具有SSH服务器的远程机器作为您的开发环境。 3.安装remote-SSH插件之后&#xff0c;vscode左侧出现电脑图标&#xff0c;即为远程服务&#xff0c;按图依次点击&#xff0c;进行服务器配置…

拿捏--->乘法口诀表

文章目录 前言左下角左上角右下角右上角等腰三角形 前言 九九乘法表,我们从小学就已经了解并且学习了,九九乘法表的历史距今已经有2千多年的历史了。那我们如何使用c语言来输出九九乘法口诀表呢,我们一起来看一看。 我们了解的九九乘法口诀表是这样的&#xff1a; 那我们如…

CPU Architecture Methodologies

MMU MMU(Memory Management Unit) 负责将逻辑地址转化为物理地址对于现代处理器来说&#xff0c;一般每个core都有自己的 MMU页表等数据结构保存在 TLB NUMA Non-uniform memory access (NUMA) is a computer memory design used in multiprocessing, where the memory access…

每日一题——只出现一次的数字(III)

只出现一次的数字——III 题目链接 注&#xff1a;本题的解法建立在位运算——异或和题目《只出现一次的数字——I》之上&#xff0c;如果对这些内容不太熟悉&#xff0c;建议先看看&#xff1a; 位运算详解 只出现一次的数字——I 思路 我们先来回顾一下异或的特性&#x…

事务的隔离级别以及传播机制的详细讲解

1.为什么需要事务&#xff1f; 事务就是将一组操作封装成一个执行单元&#xff0c;要么全部执行成功&#xff0c;要么全部执行失败 ⽐如转账分为两个操作&#xff1a; 第⼀步操作&#xff1a;A 账户 -100 元第⼆步操作&#xff1a;B 账户 100 元 如果没有事务&#xff0c;第⼀…

nodejs的字符串文字(‘‘)和模板文字(``)性能比较

nodejs的字符串文字(‘’)和模板文字()性能比较 js支持两种方式定义字符串&#xff1a; 使用 const str "Hello " "world!";使用 const worldText "world!" const str Hello ${worldText};我们可能不会太关注这些&#xff0c;应该都是怎…

同步锁: synchronized

synchronized 1. synchronized的特性2. synchronized的使用3. synchronized的锁机制 1. synchronized的特性 原子性: 所谓原子性就是指一个操作或者多个操作&#xff0c;要么全部执行并且执行的过程不会被任何因素打断&#xff0c;要么就都不执行。可见性: 可见性是指多个线程…