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

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

  • 渲染不及时,页面掉帧
  • 网页内存占用过高,运行卡顿

两种类型又可细分如下:

渲染不及时,页面掉帧

长时间占用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 的值所占用的内存

常见的造成内存泄漏的原因:

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

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

实例

<button onclick="createNode()">添加节点</button><button onclick="removeNode()">删除节点</button><div id="wrapper"></div><script>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]);  }}</script>

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

  • 闭包引起的内存泄漏

实例:

 <button onclick="replaceThing()">第二次点我就有泄漏</button><script>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<30;i++){let li = document.createElement('li')li.addEventListener('click', function(e) {let _this = e.target;let dom = e.target.tagName.toLowerCase();_this.style.color = 'red';})li.innerHTML = `</div><h4>测试图片 </h4><img style = "height:20px;width:100px" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591105123139&di=90a63b4962d0b4405ce65c2096a676c2&imgtype=0&src=http%3A%2F%2Fimg0.imgtn.bdimg.com%2Fit%2Fu%3D3769023270%2C3433174748%26fm%3D214%26gp%3D0.jpg"/></div>`d.appendChild(li)}document.getElementById('app').appendChild(d)}

上面的代码是下拉加载,每次都会添加dom,最终导致内存过大

解决办法:采用虚拟列表和事件委托

总结:页面卡顿在实际开发过程中有很多场景,可以使用内存泄漏检测工具(sIEve,针对IE)进行检测,也可以使用chrome提供的timeline和profiles,或者performance,这里不再详细介绍。

参考:https://www.cnblogs.com/yanglongbo/articles/9762359.html

https://blog.csdn.net/c11073138/article/details/84728132

js初化加载页面时ajax会调用两次的原因_在前端开发中,有哪些因素会导致页面卡顿相关推荐

  1. 移动端微信公众号页面开发中 使用了footer标签导致页面错位

    移动端微信公众号页面开发中 使用了< footer>标签导致页面错位 之前也用过这个标签没出现过此类问题 在微信开发者工具中发现把footer 改成div 样式就正常了

  2. 纯ajax html网页,JavaScript – 页面加载纯HTML AJAX网站中的替代品

    Is my goal impossible or there's a mature approach out there? 最近有很多JavaScript框架围绕这个概念("单页应用程序&q ...

  3. .ajax显示加载动画,jQuery Ajax 加载数据时异步显示加载动画

    ajax加载后台数据就不说的那么细了. 看下面代码首先前台上放置代码 在js脚本文件中首先把这个图片动画隐藏 代码如下 $(document).ready(function () { $(" ...

  4. Android插件化——加载其他APP页面

    Android插件化--加载其他APP页面 1.分析 2.优点 3.详细过程 3.1 标准化加载接口 3.2待加载的APP-B 3.3 APP-A 主加载工程 3.3.1 加载工具类PlugManag ...

  5. ajax显示加载动画,jQuery Ajax 加载数据时异步显示加载动画

    jQuery Ajax 加载数据时异步显示加载动画 ajax加载后台数据就不说的那么细了. 看下面代码首先前台上放置代码 在js脚本文件中首先把这个图片动画隐藏 代码如下 $(document).re ...

  6. 页面加载完时再动态添加脚步

    //页面加载时不存在,加载完时再添加 function loadScript(url) {//外部文件var script = document.createElement("script& ...

  7. 用手机UC浏览器页面打开vue项目,图片,css,js都没加载

    用手机UC浏览器页面打开vue项目,图片,css,js都没加载 用HBuilder X创建了一个普通的vue项目,在电脑上使用浏览器看没有什么问题,然后提交到了公司的服务器上,发现用手机,除了火狐,谷 ...

  8. 高性能js之js文件的加载与解析

    随着网站的发展,现在的网页已经离不开js,经常一个页面会引入大量的js.那么该如何合理的加载这些js? head标签中引入js文件可能是最常见的一种方式,但是这样会造成一个问题.因为j可以说是浏览器中 ...

  9. js的动态加载、缓存、更新以及复用(四)

    本来想一气呵成,把加载的过程都写了,但是卡着呢,所以只好在分成两份了. 1.页面里使用<script>来加载 boot.js . 2.然后在boot.js里面动态加载 bootLoad.j ...

最新文章

  1. Android 补间动画(Tween Animation)
  2. 使用Cydia Substrate Hook Android Java世界
  3. Select()系统调用及文件描述符集fd_set的应用
  4. Qt窗口在屏幕上居中显示
  5. 生产者和消费者代码———操作系统_操作系统基础15-生产者消费者问题
  6. Flink专题-Source
  7. 高薪必备|Redis 基础、高级特性与性能调优
  8. 如何调整金格电子章服务器印章_电子签章赋能勘察设计新动力
  9. hibernate框架 最新_Java 15 个框架
  10. 9本R语言书,从入门到进阶都在这了
  11. 操纵股价的10种手段
  12. 图像处理——空间域和频率域部分图像增强学习
  13. 自考科目列表,自考本科,题库,自学考试,历年真题
  14. 【TouchDesigner学习笔记与资料】
  15. 朋友圈一杠中间一个点_朋友圈看到这条线,大概率是被删了
  16. 6000字长文,终于将数据中台架构体系讲明白了
  17. 520表白——送她一片星空模拟
  18. 小甲鱼老师《带你学C带你飞》的后续课程补充
  19. 用数字签名实现防篡改
  20. 百度推广引流一个成本多少?百度推广怎么预估成本?

热门文章

  1. 程序员写代码的致命缺点
  2. rust程序设计语言第二版_C语言程序设计(山东联盟青岛大学版)
  3. VUE3.x的基本使用
  4. 可穿戴设备的主流传感器介绍
  5. linux下centeros7 关于mysql的下载与安装过程
  6. maven怎么强制updating_业余草 maven异常:Updating Maven Project 的统一解决方案
  7. html流光按钮,【CSS】css实现流光效果-按钮流光显示效果-自发光
  8. idw matlab 程序_IDW 算法MATLAB 实现 -
  9. unity editor里的undo操作
  10. 关于unity2019.3.11.f在烘焙光照贴图时闪退的问题