今天就来说说我在工作中所总结的和一些性能优化建议或者原则
整个建议分为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占用高的问题,
其实渲染的话在大多数的情况下肯定更消耗性能的,渲染优化好了,那么性能问题一般就解决了,所以我们一般优化性能先从优化渲染效率来做优化,然后再做程序优化
好了,大体就总结到这里,其实还有很多细节优化,但是太细节了,就不列在这里了