随着前端蓬勃发展,业务复杂性的增加,如何优化WEB页面性能,逐渐变成我们需要去解决的巨大挑战。
WEB性能优化又是一个比较宽泛的领域, 本文只以一次产品迭代需求添加大量的拖拽行为,当会话量巨大的时候, 导致严重页面卡顿为例,从三个递进的维度去思考如何去优化WEB的页面性能。
- 如何定位性能问题, 内存泄漏问题
- 如何在业务复杂的情况下,保证用户体验
- 如何处理解决性能问题
GC[Garbage Collection],可以帮助开发者自动管理内存, 定期地检查已分配内存是否可继续被追踪, 大多数垃圾回收器的实现都使用的是 mark-sweep算法, 这个算法主要步骤如下:
- 由垃圾回收器维护一系列根节点(代码中被引用的全局变量)列表,在 JS 中,window 或 global 对象就可以看作是一个根结点。由于 window 对象是一直会存在的,所以它和其子对象都会被看作是一直可被追踪的,即这些对象对应的内存块不会被回收。
- 所有的根都会被检查并标记为引用状态,从根出发,递归地检查子结点。所有可被检查到的都视为非垃圾。
- 所有未被标记的内存块都被认为是垃圾内存,即回收器将回收这一块内存并返还给 OS 重新分配。
有效引用 : 能被检查并标记, 存在引用状态
无效引用 : 被标记, 不存引用关系, 内存泄漏最常见的原因就是 无效引用。
对于GC销毁我们可以看下如下事例图
对于Javascript一般导致内存泄漏有如下几种原因:
- 意外的全局变量
- 被遗忘的计时器或回调函数
- 循环引用
- 脱离 DOM 的引用(
detached) - 闭包
使用 Chrome 和 DevTools 查找影响页面性能的内存问题,包括内存泄漏、内存膨胀和频繁的垃圾回收。一般会有如下使用技巧:
- 使用 Chrome 的任务管理器了解您的页面当前正在使用的内存量。
- 使用 Timeline 记录可视化一段时间内的内存使用。
- 使用堆快照确定已分离的 DOM 树(内存泄漏的常见原因)。
- 使用分配时间线记录了解新内存在 JS 堆中的分配时间。
- Shallow size = 本身对象大小
- Retained size = 本身对象大小 + 直接及间接引用的对象大小
Timeline 面板可以帮助直观了解页面在一段时间内的内存使用情况。
var x = [];
function grow() {
for (var i = 0; i < 10000; i++) {
document.body.appendChild(document.createElement('div'));
}
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);运行此代码会生成一个类似于以下屏幕截图的 Timeline 记录:
典型的内存泄漏定位:1. 性能参数连续上升, 2. 性能参数短时间垂直上升
Profiles有如下几种类别可以定位:
- Take Heap SnapShot[堆栈快照]
- Record Allocation Timeline [使用时间轴来定位内存堆栈的分配]
- Record Allocation Profile [函数级别定位]
只有页面的DOM树 和 Javascript代码不再引用DOM节点时, DOM节点才会被作为垃圾进行回收。
var detachedNodes;
function create() {
var ul = document.createElement('ul');
for (var i = 0; i < 10; i++) {
var li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);RAIL 是 response (响应)、 animation(动画)、idle(浏览器空置状态)和 load(加载)。
从这四个模块角度来思考你的产品。如果在每个模块上,你都可以达到性能优化的目标值(也就是上文提及的阈值),那么最终用户感受到的将会是极致的体验。这里对这些概念进行简单介绍(PS:不是本文重点):
我们必须在100ms以内对用户的输入做出响应,否则用户会感到延迟。这适用于任何输入,不管他们是在单击按钮、切换表单组件,还是在开启一个动画。
动画实际上是web应用程序一个不可避免的行动。比如,scrolling和touchmoves都是动画。如果动画的帧率是变化的,我们的用户可以真切地感受到。当前大多数设备的屏幕刷新频率都是60次/秒,因此我们的目标就是要在1秒内产生60帧,而每一帧一般都会经过以下步骤:
我们应该最大化空闲时间,然后利用空闲时间来完成一些延迟任务。
我们要在1s内加载完成我们的站点!如果没有,用户就会很迷惑,然后他们对待这个加载任务的感觉就会被破坏!
在开发的过程, 对于连续行为及短时间会触发很多次的浏览器事件,例如:dragAnddrop, scroll,resize, mousemove,我们急需对其进行性能优化。两种方式可以去优化我们的性能
函数节流(throttle) 和 函数去抖(debounce)。
作者简易实现,体现两者的思想,实现可以参考
underscore.js实现
函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用来节流。例如:连续的点击行为;
var throttle = (function(){
var previous = 0,
before = +new Date;
return function(func, wait){
var now = +new Date;
if(previous == 0){
before = +new Date;
previous++;
func();
return;
}
if(now - before > wait){
previous++;
func();
before = now;
return;
}
}
})()函数去抖就是对于一定时间段的连续的函数调用,只让其执行一次。
var dubounce = (function(){
var timer = 0;
return function(func, wait){
clearTimeout(timer);
timer = setTimeout(function(){
func()
}, wait)
}
})()