前端开发不像后端那样,很少出现有大量算法的场景,但是前端性能也是需要优化的。好的代码是保证网页平稳高性能运行的基础,结合以往开发中遇到的场景,本文对前端网页卡顿的原因进行了梳理和分析,并给出了对应的解决方法。
前端页面卡顿的原因有很多,从渲染机制和运行上可以分为两大类,分别是: 两种类型又可细分如下: 渲染不及时,页面掉帧
长时间占用js线程 页面回流和重绘较多 资源加载阻塞
内存过大导致的页面卡顿
内存泄漏导致内存过大
dom节点或事件占用内存过大
一、渲染 1,长时间占用js线程
浏览器包括js线程和GUI线程,而二者是互斥的,当长时间占用js线程时,会导致渲染不及时,出现页面卡顿。
实例1:
document.body.html('为什么不先渲染我');
//程序
$.ajax({
url: '',
async: false
})
//运行结果会在ajax执行完毕后,再去渲染页面
采用同步方式获取数据,会导致gui线程挂起,数据返回后再执行渲染。
实例2:
function a (){
// arr的长度过长时会导致页面卡顿
arr.forEach(item => {
...
})
}
let inputDom = document.getElementById('input')
let arr = document.getElementsByClassName('.tag')
input.onchange = a
计算时间过长导致页面渲染不及时
渲染不及时的原因:
浏览器的渲染频率一般是60HZ,即要求1帧的时间为1s / 60 = 16.67ms,浏览器显示页面的时候,要处理js逻辑,还要做渲染,每个执行片段不能超过16.67ms。实际上,浏览器内核自身支撑体系运行也需要消耗一些时间,所以留给我们的时间差不多只有10ms。
常见的优化方式:
实例
function Task(){
this.tasks = [];
}
//添加一个任务
Task.prototype.addTask = function(task){
this.tasks.push(task);
};
//每次重绘前取一个task执行
Task.prototype.draw = function(){
var that = this;
window.requestAnimationFrame(function(){
var tasks = that.tasks; if(tasks.length){
var task = tasks.shift();
task();
}
window.requestAnimationFrame(function(){that.draw.call(that)});
});
};
2,页面回流和重绘较多
获取scrollTop、clentWidth等维度属性时都会触发layout以获取实时的值,所以在for循环里面应该把这些值缓存一下
实例:
优化之前
for(var i = 0; i < childs.length; i++){
childs.style.width = node.offsetWidth + "px";
}
优化之后
var width = node.offsetWidth;for(var i = 0; i < childs.length; i++){
childs.style.width = width + "px";
}
当DOM结构越复杂时,需要重绘的元素也就越多。所以dom应该保持简单,特别是那些要做动画的,或者要监听scroll/mousemove事件的。另外使用flex比使用float在重绘方面会有优势。
3,资源加载阻塞 二、内存过大导致的页面卡顿 1,内存泄漏导致内存过大
浏览器有自己的一套垃圾回收机制,主流垃圾回收机制是标记清除,不过在ie中访问原生dom会采用引用计数方式机制,而如果闲置内存得不到及时回收,就会导致内存泄漏。
简单介绍下两种垃圾回收机制(GC Garbage Collection)
标记清除:
定义和用法:
当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。
到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
流程:
引用计数
定义和用法:引用计数是跟踪记录每个值被引用的次数。
基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收
的对象。
流程
常见的造成内存泄漏的原因:
解决:使用严格模式避免。
实例
text变量在createNode中引用,导致text不能被回收
实例: