跳到主要内容

机智应对后端一次性返回 10万 条数据

问题描述

  • 面试官:后端一次性返回10万条数据给你,你如何处理?
  • 我:歪嘴一笑,what the f**k!

问题考察点

看似无厘头的问题,实际上考查候选人**「知识的广度和深度」**,虽然在工作中这种情况很少遇到...

  • 考察前端如何处理大量数据
  • 考察候选人对于大量数据的性能优化
  • 「考察候选人处理问题的思考方式」(关于这一点,文末会说到,大家继续阅读)
  • ......

使用express创建一个十万条数据的接口

若是道友对express相关不太熟悉的话,有空可以看看笔者的这一篇全栈文章(还有完整代码哦):**《Vue+Express+Mysql全栈项目之增删改查、分页排序导出表格功能》**

route.get("/bigData", (req, res) => {
res.header('Access-Control-Allow-Origin', '*'); // 允许跨域
let arr = [] // 定义数组,存放十万条数据
for (let i = 0; i < 100000; i++) { // 循环添加十万条数据
arr.push({
id: i + 1,
name: '名字' + (i + 1),
value: i + 1,
})
}
res.send({ code: 0, msg: '成功', data: arr }) // 将十万条数据返回之
})

点击按钮,发请求,获取数据,渲染到表格上

html结构如下:

<el-button :loading="loading" @click="plan">点击请求加载</el-button>

<el-table :data="arr">
<el-table-column type="index" label="序" />
<el-table-column prop="id" label="ID" />
<el-table-column prop="name" label="名字" />
<el-table-column prop="value" label="对应值" />
</el-table>

data() {
return {
arr: [],
loading: false,
};
},

async plan() {
// 发请求,拿数据,赋值给arr
}

方案一: 使用定时器分组分批分堆依次渲染(定时加载、分堆思想)

  • 正常来说,十万条数据请求,需要2秒到10秒之间(有可能更长,取决于数据具体内容)
  • 而这种方式就是,前端请求到10万条数据以后,先不着急渲染,先将10万条数据分堆分批次
  • 比如一堆存放10条数据,那么十万条数据就有一万堆
  • 使用定时器,一次渲染一堆,渲染一万次即可
  • 这样做的话,页面就不会卡死了

分组分批分堆函数

  • 我们先写一个函数,用于将10万条数据进行分堆
  • 所谓的分堆其实「思想就是一次截取一定长度的数据」
  • 比如一次截取10条数据,头一次截取0~9,第二次截取10~19等固定长度的截取
  • 举例原来的数据是:[1,2,3,4,5,6,7]
  • 假设我们分堆以后,一堆分3个,那么得到的结果就是二维数组了
  • 即:[ [1,2,3], [4,5,6], [7]]
  • 然后就遍历这个二维数组,得到每一项的数据,即为每一堆的数据
  • 进而使用定时器一点点、一堆堆赋值渲染即可

分组分批分堆函数(一堆分10个)

function averageFn(arr) {
let i = 0; // 1. 从第0个开始截取
let result = []; // 2. 定义结果,结果是二维数组
while (i < arr.length) { // 6. 当索引等于或者大于总长度时,即截取完毕
// 3. 从原始数组的第一项开始遍历
result.push(arr.slice(i, i + 10)); // 4. 在原有十万条数据上,一次截取10个用于分堆
i = i + 10; // 5. 这10条数据截取完,再截取下十条数据,以此类推
}
return result; // 7. 最后把结果丢出去即可
}

创建定时器去依次赋值渲染

比如我们每隔一秒钟去赋值渲染一次

  async plan() {
this.loading = true;
const res = await axios.get("http://ashuai.work:10000/bigData");
this.loading = false;
let twoDArr = averageFn(res.data.data);
for (let i = 0; i < twoDArr.length; i++) {
// 相当于在很短的时间内创建许多个定时任务去处理
setTimeout(() => {
this.arr = [...this.arr, ...twoDArr[i]]; // 赋值渲染
}, 1000 * i); // 17 * i // 注意设定的时间间隔... 17 = 1000 / 60
}
},

这种方式,相当于在很短的时间内创建许多个定时任务去处理,定时任务太多了,也耗费资源啊。

实际上,这种方式就有了大数据量分页的思想

方案二: 使用requestAnimationFrame替代定时器去做渲染

关于requestAnimationFrame定时器优点,道友们可以看笔者的这篇文章:《**性能优化之通俗易懂学习requestAnimationFrame和使用场景举例**》

「反正大家遇到定时器的时候,就可以考虑一下,是否可以使用请求动画帧进行优化执行渲染?」

如果使用请求动画帧的话,就要修改一下代码写法了,前面的不变化,plan方法中的写法变一下即可,注意注释:

async plan() {
this.loading = true;
const res = await axios.get("http://ashuai.work:10000/bigData");
this.loading = false;
// 1. 将大数据量分堆
let twoDArr = averageFn(res.data.data);
// 2. 定义一个函数,专门用来做赋值渲染(使用二维数组中的每一项)
const use2DArrItem = (page) => {
// 4. 从第一项,取到最后一项
if (page > twoDArr.length - 1) {
console.log("每一项都获取完了");
return;
}
// 5. 使用请求动画帧的方式
requestAnimationFrame(() => {
// 6. 取出一项,就拼接一项(concat也行)
this.arr = [...this.arr, ...twoDArr[page]];
// 7. 这一项搞定,继续下一项
page = page + 1;
// 8. 直至完毕(递归调用,注意结束条件)
use2DArrItem(page);
});
};
// 3. 从二维数组中的第一项,第一堆开始获取并渲染(数组的第一项即索引为0)
use2DArrItem(0);
}

其他方案,滚动加载,分页组件加载,虚拟列表,等等