哈喽各位老铁,赋闲在家的几天又鼓捣了个新知识点,迫不及待拿出来与各位分享。🌹
最近老菜鸟碰到一个比较神奇的面试题,场景是一个列表页,如果服务端的接口返回的数据有1000多个(就是这么长,没做分页,不要做分散思考),如何能保证浏览器渲染流畅度。
心声:啧啧,多么奇葩的场景,服务没做好的事情丢给前端来搞。但是情况就是这么个情况。😜
其实如果这个问题不纠结在把任务都交给前端的话,我们有诸多良好的解决方案,比方说加BFF层,GraphQL,或者给后端小哥买奶茶,让兄弟给加个分页也不难~
但是~没有如果,就是前端er来搞,那么身为一个优秀前端工程师的你要怎么办呢?
我一开始的思路就是要么滚动?懒加载?那么加载过程中后续的数据从哪里来呢,是不是还要后端老铁做分页?如果我们一口气把这些数据都变成dom渲染到页面上,那么浏览器势必会卡顿,这是由于生成渲染树时如果一帧没渲染完,会等待下一帧,具体请参考:为什么JS同时让百八十个DOM动起来会卡顿
那么解决这个问题就有思路了:
我通过api接口拿到大量数据,把它存在本地,由前端逐步控制渲染就完了~
————前端装逼架构师 Yubble
具体流程图如下:
想必在下的这个泳道图应该能说明了吧,就是要把大量数据先都拿到端上、存起来(这个环节不涉及到浏览器渲染,所以不会出现浏览器卡顿的问题),之后少量的提取出来,做页面的Dom渲染。
需要注意点:
既然是庞大数据,存在哪儿?
存在localStorage里?小点吧,而且localStorage经常存的是什么,持久化用户信息吧,一个大个儿的cookie罢了,只是不能带上它做前后端通信。且并没有结构化读写能力,存个庞大的数据结构进去获取的时候又是个麻烦事。
这时候就到了我们的主角IndexDB登场了,结构化存储,非关系型数据库,类似mongoDB,直接键值对存在,对于前端JS是天然优势。
到这里概要都讲完了,剩下就是具体实施了~
我们实验的步骤有以下几点:
1、搭建能吐出大量数据的服务器
2、用一个前端页面去承载这些数据绘制Dom,观察页面卡顿情况
3、编写前端数据库控件,完成新建数据库,添加数据,查询数据的功能
4、尝试分批从前端数据库提取数据,查看页面渲染时卡顿情况
5、完事吃夜宵
第一步,我们先搭建服务,这里采用node,并随机生成10000条数据,每一条数据都有三个内容,分别是id,产品名称,产品价格,代码如下:
注意处理下跨域哈第二步,随便写个页面请求它,放到页面Dom上,看下渲染结果~
注意看我在点击完加载按钮之后,明显有一个加载中的时间乍一看您会不会觉得是异步加载,接口反应慢导致的,但是看接口返回其实一共才用了46毫秒,绝对毫秒级返回,且接口返回后页面依旧显示加载中文案,这将近一秒的加载中时间,其实全耗费在了浏览器渲染上。(我是本地访问,没多少消耗)
(由于老菜鸟写的list结构简单,内容单一,所以渲染慢并不是太明显,如果他们Dom层级嵌套多,内容复杂的话效果会更明显)
前端代码如下:
dom结构 js调用为什么没有显示加载完毕呢,因为js中已经拿到所有数据了,eventloop在跑到这个异步请求返回时data已经完成数据承载了,下一次的eventloop tick的时候虚拟dom已经备好了渲染list的状态,只不过浏览器重绘量太大,一时反应不过来,所以就停留在'加载中'。加油,我们已经验证一半了~
第三步,编写前端数据库,实现建库,添加内容,查询等功能
这一步我们就要上活儿了,这篇博文不是教学哈,主要还是展示调研成果为主,所以关于IndexDB的各类api还得各位客爷自行学习,或者直接照着在下现成的敲一番也行,反正我也是找的网上现成的。😏
首先我们新建一个.js文件,用来放操作IndexDB的所有方法模块,这里要是在实际开发中直接写在业务中,你看组长打不打你。
其中包括三个主要方法,打开数据库(open),添加数据(inser),查询数据(query)
大致结构如下:
注意getStoreByTransaction是通过数据库的'事务'搭一座桥,拿到数据库,才能对其增删改查进行操作。只将open方法暴露出去,因为查询或插入的前提,必须先将数据库打开,任务流不能乱哈。我们来一起看看open方法
其中比较有价值的地方就在于IndexedDB.open()方法返回的请求对象request,它有三个回调,分别是开启成功(onsuccess),开启失败(onerror),以及新建库之后的回调(onupgradeneeded)。
其中onupgradeneeded这个回调比较关键,数据库版本更新或新建库时就会运行它,在这里可以看下是否有历史的旧库,如果有就删掉。然后我们开始创建商品表,并给与id为主键,咱们分别创建两个索引,商品名称和商品价格(就是代码中的indexArr队列)
而onsuccess中我们做的就很简单,将对于数据库的操作api全部暴露出去,相当于给了业务一个数据库的遥控器。
我们再来看看插入方法怎么写的
过于简单不解释了吧看下查询方法
查询方法里我们就要接触到一个特别的概念了,即游标(cursor)。
这个家伙事儿是数据查询时必要的一个元素,试想一下,如果我们想要快速定位一条信息,除了要知晓信息详细内容外,通过队列的下标查找也是常用方案。所以他提供了IDBKeyRange,用于通过下标或者具体的值来定位一条信息的游标。
再通过openCursor这个方法打开这个游标,就可以在onsuccess这个回调中拿到这一条信息具体内容。
如果还需要即系定位到它的下一条信息则直接在这个内容对象上调用.continue()方法,因此可一口气刷出一个游标后置的N条信息结果。
如果客爷您之前有用过mongoDB,那么这一块上手应该很简单,没用过的客爷也可以参照上图查询10条的逻辑自己敲一下,一目了然~
第四步,尝试将数据全部灌入浏览器数据库,并分批渲染
此处加上了对于数据库的几处操作我们来看下实验效果
此时我把数据全都拉下来存到IndexDB中,再从中获取10条信息渲染到页面上,还是会有明显加载中的闪现,不过可以看到比之前流畅多了,如果数据结构复杂,层级多的话,效果会更明显。
毕竟操作的是大量结构数据库,为避免阻塞,indexDB的操作还都放在了eventLoop的异步操作中,所以还是会有明显异步痕迹,让人能看到加载中的字样。
我如果把上一页、下一页的切换加上,效果就更明显了,基本上看不出延时效果
上一页和下一页逻辑以上就是用IndexDB缓存前端大量数据的调研全纪录,如果考虑到数据的实时性,就要考虑让服务给数据加上版本控制,或者每次前端在页面刷新时重新获取一遍数据整体内容。
总结下吧,IndexDB解决的最大问题就是:页面承载内容太多时,可以让浏览器做一个缓冲,减少浏览器压力。
以上就是我们对IndexDB的调研全过程了,希望各位客爷有所收获。
这篇文章我调研、实验、编写花了大概4、5天的时间,要单说IndexDB起码还能多写两篇,所以本文还是主要以调研为主,api的使用还是请各位客爷查看官方文档,在此感谢下“熊的猫”的用IndexDB实现分页一文。
写完这篇博文让我想起了之前面试的一位小哥,大概6、7年开发经验,但是多在功能实现层面,整体没什么立体架构思维,面试结束后垂头丧气~和我说自己知识面不够,一直写业务没人带,没什么成长~
虽然最后的确gg了,但是我却不是很赞同他的观点,我们所处的行业日新月异,工具框架过几天出一个,谁也不能面面俱到,所以不能因为一两次面试得失而否定自己啊,只能说面的这个岗位和候选人经验及能力不是很匹配,一个做了3、4年架构的工程师肯定没有专心写业务的同学开发快,一个只写业务逻辑的工程师对架构设计思维也一定是贫瘠的。
所以我还是固执的认为,没有什么能力强弱,只是和当前职位匹不匹配罢了。
毕竟能干这行的,没有自学能力弱的~
网友评论