在前端开发中,有哪些因素会导致页面卡顿 您所在的位置:网站首页 为什么oppo手机会卡在一个页面 在前端开发中,有哪些因素会导致页面卡顿

在前端开发中,有哪些因素会导致页面卡顿

2024-07-08 06:53| 来源: 网络整理| 查看: 265

前端开发不像后端那样,很少出现有大量算法的场景,但是前端性能也是需要优化的。好的代码是保证网页平稳高性能运行的基础,结合以往开发中遇到的场景,本文对前端网页卡顿的原因进行了梳理和分析,并给出了对应的解决方法。

前端页面卡顿的原因有很多,从渲染机制和运行上可以分为两大类,分别是:

渲染不及时,页面掉帧

网页内存占用过高,运行卡顿

两种类型又可细分如下: 渲染不及时,页面掉帧

长时间占用js线程 页面回流和重绘较多 资源加载阻塞

内存过大导致的页面卡顿

内存泄漏导致内存过大

意外的全局变量引起的内存泄漏

闭包引起的内存泄漏

被遗忘的定时器

循环引用

DOM删除时没有解绑事件

没有清理的DOM元素引用

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。

常见的优化方式:

采用requestIdleCallback和requestAnimationFrame,任务分片

实例

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,页面回流和重绘较多

尽量减少layout

获取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结构越复杂时,需要重绘的元素也就越多。所以dom应该保持简单,特别是那些要做动画的,或者要监听scroll/mousemove事件的。另外使用flex比使用float在重绘方面会有优势。

3,资源加载阻塞

js资源放在body之前

行内script阻塞

css加载会阻塞DOM树渲染(css并不会阻塞DOM树的解析)

资源过大阻塞

二、内存过大导致的页面卡顿 1,内存泄漏导致内存过大

浏览器有自己的一套垃圾回收机制,主流垃圾回收机制是标记清除,不过在ie中访问原生dom会采用引用计数方式机制,而如果闲置内存得不到及时回收,就会导致内存泄漏。

简单介绍下两种垃圾回收机制(GC Garbage Collection)

标记清除:

定义和用法:

当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。

到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

流程:

浏览器在运行的时候会给存储再内存中的所有变量都加上标记

去掉环境中的变量以及被环境中引用的变量的标记

如果还有变量有标记,就会被视为准备删除的变量

垃圾回收机制完成内存的清除工作,销毁那些带标记的变量,并回收他们所占用的内存空间

引用计数

定义和用法:引用计数是跟踪记录每个值被引用的次数。

基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收

的对象。

流程

声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值引用次数就是1

同一个值又被赋值另一个变量,这个引用类型的值引用次数加1

当包含这个引用类型值得变量又被赋值另一个值了,那么这个引用类型的值的引用次数减1

当引用次数变成0时, 说明这个值需要解除引用

当垃圾回收机制下次运行时,它就会释放引用次数为0 的值所占用的内存

常见的造成内存泄漏的原因: 意外的全局变量引起的内存泄漏

解决:使用严格模式避免。

实例

添加节点 删除节点 var text = []; function createNode() { text.push(new Array(1000000).join('x')); var textNode = document.createTextNode("新节点"), div = document.createElement('div'); div.appendChild(textNode); document.getElementById("wrapper").appendChild(div); } function removeNode() { var wrapper = document.getElementById("wrapper"), len = wrapper.childNodes.length; if (len > 0) { wrapper.removeChild(wrapper.childNodes[len - 1]); } }

text变量在createNode中引用,导致text不能被回收

闭包引起的内存泄漏

实例:

第二次点我就有泄漏 var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) { console.log("hi"); }; } theThing = { longStr: new Array(1000000).join('*'), someMethod: function someMethod() { console.log('someMessage'); } }; };

上面那段代码泄漏的原因在于有两个闭包:unused和someMethod,二者共享父级作用域。

因为后面的 theThing 是全局变量,someMethod是全局变量的属性,它引用的闭包作用域(unused 和somMethod共享)不会释放,由于originalThing在共享的作用域中,造成originalThing不会释放,随着 replaceThing 不断调用,originalThing 指向前一次的 theThing,而新的theThing.someMethod又会引用originalThing ,从而形成一个闭包引用链,而 longStr是一个大字符串,得不到释放,从而造成内存泄漏。

解决方法:在 replaceThing 的最后添加 originalThing = null

被遗忘的定时器

实例

var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);

计时器回调函数没被回收(计时器停止才会被回收)

循环引用

循环引用就是对象A中包含另一个指向对象B的指针,B中也包含一个指向A的引用。

因为IE中的BOM、DOM的实现使用了COM,而COM对象使用的垃圾收集机制是引用计数策略。所以会存在循环引用的问题

解决方法:手工断开js对象和DOM之间的链接。赋值为null。

实例:

function handle () { var element = document.getElementById(“testId”); element.onclick = function (){ alert(element.id) } }

element绑定的事件中引用了element上的属性

onclick事件是一个闭包,闭包可以维持函数内局部变量,使其得不到释放。也就是说element变量得不到释放,每调用一次element都会得不到释放,最终内存泄漏

解决方法:

function handle () { var element = document.getElementById(“testId”); element.onclick = function (){ alert(element.id) } element = null } DOM删除时没有解绑事件

比如删除一个button,但是并没有解除button上的事件

没有清理的DOM元素引用 2,dom节点或事件占用内存过大

详细分析见我另外一篇文章 网页dom元素过多为什么会导致页面卡顿

实例:

function addDom(){ let d = document.createDocumentFragment(); for(var i = 0;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有