前端 性能优化建议

时间:2022-06-01 20:01:57

今天就来说说我在工作中所总结的和一些性能优化建议或者原则

整个建议分为3个大块:1. 加载优化  2. 减少重排和重绘  3.程序优化

 

在介绍建议之前,先说一下 在浏览器输入一个网址之后的请求过程,比如输入baidu.com

1. 把域名解析成ip地址,好请求对应服务器

实际上请求服务器的地址都是ip地址,但是ip太难记住了,所以就用域名的形式来呈现url地址,这样方便大家好记住

解析的过程:1)查找浏览器缓存

        2)查找os(系统)缓存,也就是host文件里面存储的地址

        3)查找本地dns服务器

      4)查找dns服务商的服务器

      5)在根服务中递归查找

注:上面的每一步如果成功就直接返回,否则就继续执行下一步

 

2. 发送一个http请求到对应服务器,进行3此握手过程

如果需要重定向的话(比如上面输入的 baidu.com 就需要重定向)服务器根据实际情况返回一个以3开头的重定向状态码

 

3. 服务器处理request 请求,调用对应的端口的程序来处理这个消息,生成cookie

 

4. 服务器返回处理结果,response body 根据请求不同,而返回不同,有可能是html,有可能是图片,js或css等等,如baidu.com对应请求

返回resonse body的对应的为压缩后的html

5. 浏览器开始呈现

 呈现过程为:1)解析html,生成dom树,在解析css和js对应的节点,会加载对应的文件,他们加载的区别就是,css是不暂停解析,js文件会暂停解析过程

         2) 当css文件加载完成后,会解析css文件,生成渲染树

       3)渲染树生成完成后,计算渲染树对应的每个节点的宽高重新布局

         4)开始渲染,会起一个新的线程来做渲染的事

 

注:dom树和渲染树不一样,dom树里面的一个节点,有可能对应渲染树里面的多个节点,比如html节点会对应渲染树里面的,viewport,scroll,block节点,

  也有可能dom树里面的节点不在渲染树里面,比如display:none的节点

 

以上就是一个请求过程,和性能优化有很大的关系        
  

一   加载优化 ,减少http请求

1) 合并压缩文件

合并压缩css文件,js文件,可以有效的成倍的减少http请求,还可以对图片进行压缩,然后再合并到一张图片上,再利用background-position属性来定位

2)利用缓存,减少下载时间

3)使用cdn,可以减少服务器压力,并突破http最大连接数的限制

4)将js文件放在body最后,因为当解析加载js文件的时候,会暂停解析dom树渲染等操作,如果放在最后的话可以避免这样

5)延迟加载,对不是需要在界面实现后立即用到的文件进行延迟加载,这样可以提高用户看到页面的速度

可以对tag标签使用defer属性(这个属性不是每个浏览器的版本都支持),也可以使用加载器在页面加载完成后再去加载不会修改dom的文件

6)预加载,对一些常用的资源,在空闲的时候进行预加载,当之后用到的时候就不用加载就直接显示了

7)对某些可能用到也可能用不到的资源实行按需加载

8)动态加载,这样就不会阻塞浏览器执行操作了,可以使用加载器,也可以自己写,就是创建一个script元素,设置src为目标路径,然后appendChild到head元素中

 

二  减少重排和重绘

 重排和重绘是指上面请求流程的5中的3)和4),它们可以说是最消耗性能的地方了,所以我们要尽可能的减少触发他们的次数

1)最好将修改dom元素的代码集中在一起,这样就只会引起一次重排和重绘

因为浏览器的优化策略,会将多次修改都放入一个队列,然后一次性修改

2)尽量少访问一些会让浏览器立即刷新的属性,因为从1)可以得知,浏览器会优化刷新次数,如果调用了相关属性让浏览器强制立即刷新,那么肯定更耗性能,

可以缓存这些属性,方便多次调用

具体的一些属性:1)offsetTop, offsetLeft, offsetWidth, offsetHeight
        2)scroll。。。
        3)client。。。
        4)getComputedStyle() 

注:2)和3)的每个属性是开头单词不一样其他都一样,比如scrollTop

3)利用cssText或者class,将需要改变的样式一次性写入

4)利用cloneNode,createDocumentFragment或者display:none 进行离线dom操作,这样可以只触发1次或者2次重排和重绘

5)将动画脱离文档流

因为动画更新的十分频繁,如果不脱离的话,会一直引起这动画之后的dom元素的重排和重绘,非常耗性能,如果脱离文档流了,就只会重排和重绘动画本身对应的dom

元素,另外如果动画的呈现是改变定位元素的话,如果某元素从左往右移动一定距离,是改变left属性,然后我们可以利用translate替代实现,这样效率更高

 

 

三   js优化

1)避免重复计算,特别是在多次执行的代码块中,比如循环

//假定有一个数组是ary,
for(var i = 0; i < ary.lenth; i++){
//do something
}

//我们可以换一种方式来,下面的方式就知调用了一次length
for(var i = ary.lenth - 1; i >=0; i--){
// do something
}

上面是我们很常会用到的一种循环方式,do something 代码中如果存在只需要计算一次的值,我们也可以在循环体外面计算一次并保持到一个变量中去

2) 使用合适的判断结构体

1.在判断条件比较少的情况下,可以使用if else,并且把出现几率最高的判断放在最前面

2.判断条件相对较多的情况下,使用switch

3.非常多的话,我们可以使用查表法,比如:

var result, temp = "c";

switch(temp){
case "a": result = "this is result a";break;
case "b": result = "this is result b";break;
case "c": result = "this is result c";break;
.... default: result = "this is result default";break;
}

//可以考虑下面的实现
var dic = {
a: "this is result a",
b: "this is result b",
c: "this is result c",
... default: "this is result default"
};

var temp = "c";
var result = dic[temp] || dic["default"];

当判断条件比较多的时候,用查表法可以有效的快速查到对应结果,当然,实际的情况肯定这个复杂的多,dic里面存储的值根据实际情况改变,甚至可以存储函数

 

3)在有条件的情况下,更多的利用原生方法,效率更高

比如查找一个元素,我们可以使用 1.getElementById,getElmentsByTagName,getElementsByClassName

                2.querySelector,querySelectorAll

像jquery查找元素的内部实现,也是优先使用1,然后是2对应的方法,如果1,2的方法都不支持的话,才执行sizzel引擎的解析方法

 

4)减少访问的层级

1. 减少作用域链中的访问层级,比如我们常用到的window属性,它就属于global作用域中,如果这个作用域链非常长,那么就相对比较消耗时间了,需要从当前

作用域一直遍历到global作用域中才会取到这个属性,我们可以缓存window属性,可以减少遍历次数

注:作用域链解释:当初始化js代码的时候,会创建一个global执行环境栈,其内部有一个Scope属性就是作用域,包含了vo,this,argument等参数,而vo包含

了形参,var定义的参数,定义的function等,当执行一个方法或函数的时候,也会创建一个执行环境栈,也会有作用域,且有一个隐藏的类似于parent的属性指向

上一个作用域也就是global作用域,这样就形成了一个作用域链,当然在方法或函数也可以继续执行方法,就会继续添加对应作用域到作用域链末端去

 

2.减少原型链中的访问层级,这个原理就和作用域链差不多

3.减少对象的访问层级,比如

var data = {
persons:{
[{
sex:"男",
name:"tanghansan"
},
{
sex:"女",
name:"hanmeimei"
}]
}
}

var personInfo = "";
for(var i = 0; i<data.persons.length; i++){
personInfo += data.persons[i].sex + "---" + data.persons[i].name +"\n";
}

//上面的循环我们可以改成
var personInfo = "", persons = data.persons;
for(var i = 0; i < persons.length; i++){
var tempPerson = persons[i];
personInfo += tempPerson .sex + "---" + tempPerson .name +"\n";
}
//[]运算相对而言 比性能消耗要高一些,所以我们最好避免多次用[]运算

console.log(personInfo);

  

4) 谨慎操作dom

1.最好缓存需要多次访问的dom元素以避免重复获取它,

2.最好把需要设置的内容先用一个临时变量缓存,当内容都设置完成后,再一次性的设置到dom元素中去

3.对html集合,最好转化成数组来操作,每次调用htlm集合的length属性都会重新运行查询程序去计算一次

注:什么叫html集合?比如调用getElementsByClassName方法的返回的就是一个html集合,

4.谨慎访问会引起立即重排和重绘的一些属性,比如offsetWidth等,在第二点的2)中有提到

5.当需要获取一个层级比较复杂的需要调用多次方法才能获取到的dom元素,我们可以利用 querySelector或querySelectorAll

 

5)利用事件委托添加事件侦听

具体操作就是:对父节点添加事件侦听,根据事件流的机制(捕获,目标,冒泡),其子节点也会触发事件,这样就可以避免大量添加事件,

一般的情况下,我们需要在添加节点的时候添加事件监听,删除子节点的时候删除事件监听,而用事件委托的话,我们不需要特殊的处理它

6)善用数组的join 和字符串的split方法进行 数组和字符串的相互转化

7)做动画优先使用css动画,毕竟是原生的,效率更高,如果特定场合需要使用js实现,可以尝试使用requestanimationframe替代setTimeout和setInterval

8)利用对象池技术,缓存一些创建对象消耗比较大的一些对象

9)利用算法,这个需要找具体的问题就具体分析用哪个算法了

10)最后在说一个究极大招,当你感觉程序部分其他地方已经优化不动了, 就用cpu换内存,内存换cpu的思想来做程序优化,说到底,就程序而言,性能问题就是内存和cpu占用高的问题,

其实渲染的话在大多数的情况下肯定更消耗性能的,渲染优化好了,那么性能问题一般就解决了,所以我们一般优化性能先从优化渲染效率来做优化,然后再做程序优化

 

 好了,大体就总结到这里,其实还有很多细节优化,但是太细节了,就不列在这里了