前段时间有朋友问我一个他们公司遇到的问题, 说是后端因为某种缘由没有实现分页功能, 因此一次性返回了2万条数据,让前端用select组件展现到用户界面里. 我听完以后立马明白了他的困惑, 若是经过硬编码的方式去直接渲染这两万条数据到select中,确定会卡死. 后面他还说须要支持搜索, 也是前端来实现,我顿时产生了兴趣. 当时想到的方案大体以下:javascript
懒加载和分页方式通常用于作长列表优化, 相似于表格的分页功能, 具体思路就是用户每次只加载能看见的数据, 当滚动到底部时再去加载下一页的数据.前端
虚拟滚动技术也能够用来优化长列表, 其核心思路就是每次只渲染可视区域的列表数,当滚动后动态的追加元素并经过顶部padding来撑起整个滚动内容,实现思路也很是简单.vue
经过以上分析其实已经能够解决朋友的问题了,可是最为一名有追求的前端工程师, 笔者认真梳理了一下,并基于第一种方案抽象出一个实际的问题:java
如何渲染大数据列表并支持搜索功能?node
笔者将经过模拟不一样段位前端工程师的实现方案, 来探索一下该问题的价值. 但愿能对你们有所启发, 学会真正的深刻思考.react
笔者将经过不一样经验程序员的技术视角来分析以上问题, 接下来开始咱们的表演.程序员
在开始代码以前咱们先作好基础准备, 笔者先用nodejs搭建一个数据服务器, 提供基本的数据请求,核心代码以下:web
app.use(async (ctx, next) => {
if(ctx.url === '/api/getMock') {
let list = []
// 生成指定个数的随机字符串
function genrateRandomWords(n) {
let words = 'abcdefghijklmnopqrstuvwxyz你是好的嗯气短前端后端设计产品网但考虑到付款啦分手快乐的分类开发商的李开复封疆大吏师德师风吉林省附近',
len = words.length,
ret = ''
for(let i=0; i< n; i++) {
ret += words[Math.floor(Math.random() * len)]
}
return ret
}
// 生成10万条数据的list
for(let i = 0; i< 100000; i++) {
list.push({
name: `xu_0${i}`,
title: genrateRandomWords(12),
text: `我是第${i}项目, 赶快🌀吧~~`,
tid: `xx_${i}`
})
}
ctx.body = {
state: 200,
data: list
}
}
await next()
})
复制代码
以上笔者是采用koa实现的基本的mock数据服务器, 这样咱们就能够模拟真实的后端环境来开始咱们的前端开发啦(固然也能够直接在前端手动生成10万条数据). 其中genrateRandomWords方法用来生成指定个数的字符串,这在mock数据技术中应用不少, 感兴趣的盆友能够学习了解一下. 接下来的前端代码笔者统一采用react来实现(vue同理).算法
直接从后端请求数据, 渲染到页面的硬编码方案,思路以下: json
fetch(`${SERVER_URL}/api/getMock`).then(res => res.json()).then(res => {
if(res.state) {
data = res.data
setList(data)
}
})
复制代码
{
list.map((item, i) => {
return <div className={styles.item} key={item.tid}> <div className={styles.tit}>{item.title} <span className={styles.label}>{item.name}</span></div> <div>{item.text}</div> </div>
})
}
复制代码
const handleSearch = (v) => {
let searchData = data.filter((item, i) => {
return item.title.indexOf(v) > -1
})
setList(searchData)
}
复制代码
这样作本质上是能够实现基本的需求,可是有明显的缺点,那就是数据一次性渲染到页面中, 数据量庞大将致使页面性能极具下降, 形成页面卡顿.
做为一名有必定经验的前端开发工程师,必定对页面性能有所了解, 因此必定会熟悉防抖函数和节流函数, 并使用过诸如懒加载和分页这样的方案, 接下来咱们看看中级工程师的方案:
经过这个过程的优化, 代码已经基本可用了, 下面来介绍具体实现方案:
在滚动的过程汇总咱们还须要注意一个问题就是当用户往回滚动时, 其实是不须要作任何处理的,因此咱们须要加一个单向锁, 具体代码以下:
function scrollAndLoading() {
if(window.scrollY > prevY) { // 判断用户是否向下滚动
prevY = window.scrollY
if(poll.current.getBoundingClientRect().top <= window.innerHeight) {
// 请求下一页数据
}
}
}
useEffect(() => {
// something code
const getData = debounce(scrollAndLoading, 300)
window.addEventListener('scroll', getData, false)
return () => {
window.removeEventListener('scroll', getData, false)
}
}, [])
复制代码
其中prevY存储的是窗口上一次滚动的距离, 只有在向下滚动而且滚动高度大于上一次时才更新其值.
至于分页的逻辑, 原生javascript实现分页也很简单, 咱们经过定义几个维度:
有了这几个条件,咱们的基本能分页功能就能够完成了. 前端分页的核心代码以下:
let data = [];
let curPage = 1;
let pageSize = 16;
let prevY = 0;
// other code...
function scrollAndLoading() {
if(window.scrollY > prevY) { // 判断用户是否向下滚动
prevY = window.scrollY
if(poll.current.getBoundingClientRect().top <= window.innerHeight) {
curPage++
setList(searchData.slice(0, pageSize * curPage))
}
}
}
复制代码
function debounce(fn, time) {
return function(args) {
let that = this
clearTimeout(fn.tid)
fn.tid = setTimeout(() => {
fn.call(that, args)
}, time);
}
}
复制代码
const handleSearch = (v) => {
curPage = 1;
prevY = 0;
searchData = data.filter((item, i) => {
// 采用正则来作匹配, 后期支持前端模糊搜索
let reg = new RegExp(v, 'gi')
return reg.test(item.title)
})
setList(searchData.slice(0, pageSize * curPage))
}
复制代码
须要结合分页来实现, 因此这里为了避免影响源数据, 咱们采用临时数据searchData来存储. 效果以下:
做为一名久经战场的程序员, 咱们应该考虑更优雅的实现方式,好比组件化, 算法优化, 多线程这类问题, 就好比咱们问题中的大数据渲染, 咱们也能够用虚拟长列表来更优雅简洁的来解决咱们的需求. 至于虚拟长列表的实现笔者在开头已经点过,这里就不详细介绍了, 对于更大量的数据,好比100万(虽然实际开发中不会遇到这么无脑的场景),咱们又该怎么处理呢?
第一个点咱们可使用js缓冲器来分片处理100万条数据, 思路代码以下:
function multistep(steps,args,callback){
var tasks = steps.concat();
setTimeout(function(){
var task = tasks.shift();
task.apply(null, args || []); //调用Apply参数必须是数组
if(tasks.length > 0){
setTimeout(arguments.callee, 25);
}else{
callback();
}
},25);
}
复制代码
这样就能比较大量计算致使的js进程阻塞问题了.更多性能优化方案能够参考笔者以前的文章:
咱们还能够经过web worker来将须要在前端进行大量计算的逻辑移入进去, 保证js主进程的快速响应, 让web worker线程在后台计算, 计算完成后再经过web worker的通讯机制来通知主进程, 好比模糊搜索等, 咱们还能够对搜索算法进一步优化,好比二分法等,因此这些都是高级工程师该考虑的问题. 可是必定要分清场景, 寻找出性价比更高的方案.
若是想学习更多前端技能,实战和学习路线, 欢迎在公众号《趣谈前端》加入咱们的技术群一块儿学习讨论,共同探索前端的边界。