一、数据过多导致页面卡顿
采用懒加载+分页(前端维护懒加载的数据分发和分页)类似于表格的分页功能,具体思路就是用户每次只加载能看见的数据,当滚动到底部时再去加载下一页的数据。
使用虚拟滚动技术(目前react的antd4.0已支持虚拟滚动的select长列表)
React官网推荐我们使用react-window和react-virtualized这2个热门的虚拟滚动库。它们提供了多种可复用的组件,用于展示列表、网格和表格数据。
(1). 用ajax获取到需要处理的数据, 共13万条
(2). 将数组分组,每组500条,一共260组
(3). 循环这260组数据,分别处理每一组数据, 利用setTimeout函数开启一个新的执行线程(异步),防止主线程因渲染大量数据导致阻塞。
---------------------------------------------------------------------------------------------------------------------------------
懒加载 + 分页方案
懒加载的实现主要是通过监听窗口的滚动,当某一个占位元素可见之后去加载下一个数据。
通过监听 window 的 scroll事件,以及对 poll元素 使用 getBoundingClientRect 来获取 poll元素相对于可视窗口的距离,从而实现一个懒加载方案。其中 prevY 存储的是窗口上一次滚动的距离,只有在向下滚动并且滚动高度大于上一次时才更新其值。
getBoundingClientRect()方法:用于获取某个元素相对于视窗的位置集合。
集合中有 top, right, bottom, left 等属性。
function scrollAndLoading() {if(window.scrollY > prevY) { // 判断用户是否向下滚动prevY = window.scrollYif(poll.current.getBoundingClientRect().top <= window.innerHeight) {// 请求下一页数据}}}useEffect(() => {// something codeconst getData = debounce(scrollAndLoading, 300)window.addEventListener('scroll', getData, false)return () => {window.removeEventListener('scroll', getData, false)}}, [])
分页方案:定义 curPage:当前的页数、pageSize:每一页展示的数量、data:传入的数据量。
let data = [];let curPage = 1;let pageSize = 16;let prevY = 0;// other code...function scrollAndLoading() {if(window.scrollY > prevY) { // 判断用户是否向下滚动prevY = window.scrollYif(poll.current.getBoundingClientRect().top <= window.innerHeight) {curPage++setList(searchData.slice(0, pageSize * curPage))}}}
数据分组并利用 setTimeout:通过取余数将数据分组,然后把数据一段一段的显示到页面,
var Search = { //列表数据搜索data() {return {serarchWord: '',listData: [],}},methods: {// 获取数据getData() {axios({url: '/mockview/appdesign?path=%2Fapi%2Flistdata&api=%2Fapi%2Flistdata',method: 'GET',params: {keyWord: this.serarchWord}}).then(res => {// this.listData = res.data.datalet data = res.data.datalet groupData = this.group(data)for (let i = 0; i < groupData.length; i++) {setTimeout(() => {this.listData.push(...groupData[i])}, 1000 * i);}})},group(data) { //对数据进行分组var result = [];var groupItem;for (var i = 0; i < data.length; i++) {if (i % 1000 == 0) {groupItem != null && result.push(groupItem);groupItem = [];}groupItem.push(data[i]);}result.push(groupItem);return result;},inputChange() {this.debounceSearch()},debounce(fn, wait) { //防抖var timeout = null;return function () {if (timeout !== null)clearTimeout(timeout);timeout = setTimeout(fn, wait);}}},mounted() {this.debounceSearch = this.debounce(this.getData, 800)},}
二、DOM操作造成的页面卡顿问题及解决
1.合并多次的DOM操作为单次的DOM操作
2.把DOM元素离线或隐藏后修改(适合那些需要大批量修改DOM元素的情况)
这样只会在DOM元素脱离和添加 or 隐藏和显示 时,才会造成页面的重绘或回流,对脱离了页面布局流的DOM元素操作就不会导致页面的性能问题。具体的方式主要有三种:
(1)将频繁的操作改为一次性操作,使用文档片段 :document.createDocumentFragment()
创建一个文档片段,并在此片段上进行必要的DOM操作,操作完成后将它附加在页面中。对页面性能的影响只存在于最后把文档片段附加到页面的这一步操作上。
因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
//频繁操作const list = document.getElementById('list')for (let i = 0; i < 10; i++) {const li = document.createElement('li')li.innerHTML = `List item ${i}`list.appendChild(li) //插入 10 次}// ------------------------------------------//通过创建一个新的空白的文档片段( DocumentFragment),最后一次插入const list = document.getElementById('list')const frag = document.createDocumentFragment() //关键语句for (let i = 0; i < 10; i++) { const li = document.createElement('li') li.innerHTML = `List item ${i}`frag.appendChild(li) //插入 10 次}//都完成后,再传DOM树中list.appendChild(frag)
(2)通过设置DOM元素的 display: none 来隐藏元素
通过隐藏页面的DOM元素,达到在页面中移除元素的效果,经过大量的DOM操作后恢复元素原来的display样式。
var myElement = document.getElementById('myElement');myElement.style.display = 'none';// 一些基于myElement的大量DOM操作myElement.style.display = 'block';
(3)克隆DOM元素到内存中
把页面上的DOM元素克隆一份到内存中,然后再在内存中操作克隆的元素,操作完成后使用此克隆元素替换页面中原来的DOM元素。这样一来,影响性能的操作就只是最后替换元素的这一步操作了,在内存中操作克隆元素不会引起页面上的性能损耗。
var old = document.getElementById('myElement');var clone = old.cloneNode(true);// 一些基于clone的大量DOM操作old.parentNode.replaceChild(clone, old);
3. 设置具有动画效果的DOM元素的position属性为fixed或absolute(提高动画效果的展示性能)
把页面中具有动画效果的元素设置为绝对定位,使得元素脱离页面布局流,从而避免了页面频繁的回流,只涉及动画元素自身的回流了。
4. 对DOM查询做缓存
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {//每次循环,都会计算length,频繁进行DOM操作}//------------------------------------------//缓存 DOM 查询结果const pList = document.getElementsByTagName('p')const length = pList.lengthfor (let i = 0; i < length; i++) {//缓存length,只进行一次DOM查询}
5. 使用 事件代理/委托 方式绑定事件
利用事件冒泡机制,只在父元素上绑定事件处理,用于处理所有子元素的事件,这样就不需要给每个子元素都绑定事件。
// 获取父节点,并添加一个click事件document.getElementById('list').addEventListener("click",function(e) { if(e.target && e.target.nodeName.toUpperCase == "LI") {// 针对子元素的处理}});
三、请求突增导致页面崩溃
1、开启CDN加速(服务器配置完全充足,但是网站访问速度明显变慢了)
把源站内容分发至各地最接近用户的节点;缩短用户到节点的物理距离,使用户可就近取得所需内容,降低延迟。简单来说,当一个网站开启了CDN加速,能给用户的感觉是访问网站速度或者下载东西的速度会明显比没有开启加速更快了。
高防CDN可以自动识别恶意攻击流量,对这些虚假流量进行智能清洗,将正常访客流量回源到源服务器IP上,保障源服务器的正常稳定运行。
2、确认服务器硬件是否足够支持当前的流量。(服务器配置明显不够用,导致宕机或者访问出现错误。)
进行服务器扩容:配置一台更高性能的专用服务器。
3、优化数据库访问。
服务器的负载过大,一个重要的原因是CPU负荷过大,降低服务器CPU的负荷,才能够有效打破瓶颈。而使用静态页面可以使得CPU的负荷最小化。前台实现完全的静态化当然最好,可以完全不用访问数据库,不过对于频繁更新的网站,静态化往往不能满足某些功能。
缓存技术就是另一个解决方案,就是将动态数据存储到缓存文件中,动态网页直接调用这些文件,而不必再访问数据库,WordPress和Z-Blog都大量使用这种缓存技术。
如果确实无法避免对数据库的访问,那么可以尝试优化数据库的查询SQL,避免使用 Select * from 这样的语句,每次查询只返回自己需要的结果,避免短时间内的大量SQL查询。
4、禁止外部的盗链。
外部网站的图片或者文件盗链往往会带来大量的负载压力,因此应该严格限制外部对于自身的图片或者文件盗链,目前可以简单地通过refer来控制盗链,Apache自己就可以通过配置来禁止盗链,IIS也有一些第三方的ISAPI可以实现同样的功能。当然,伪造refer也可以通过代码来实现盗链, 不过目前蓄意伪造refer盗链的还不多,可以先不去考虑,或者使用非技术手段来解决,比如在图片上增加水印。
5、控制大文件的下载。
大文件的下载会占用很大的流量,并且对于非SCSI硬盘来说,大量文件下载会消耗CPU,使得网站响应能力下降。因此,尽量不要提供超过2M的大文件下载, 如果需要提供,建议将大文件放在另外一台服务器上。
6、使用不同服务器分流主要流量
将文件放在不同的主机上,提供不同的镜像供用户下载。