前端性能优化
优化的分类:
- 网页初始过程中的优化。即用户输入域名/ip按下请求按钮,到最终网页可以使用的过程。
- 用户交互过程的优化。即用户完全加载后,用户进行操作交互的过程。
优化着手点:
- 网络资源请求和加载类,js、css、图片资源等请求过程的优化。
- 网页渲染
- js脚本
网络资源请求的优化
css和js资源的优化
- 内联
- 优点:减少http请求
- 缺点:
- 没办法复用
- 似的HTML文件变大,加载时间边长
- 都写在HTML中,不利于后期维护(目前是有手段可以在开发阶段进行外部引入,然后在代码上线时候解析成内联样式)
- 外部引入
- 优缺点和内联相反
优化方式:
- 到底使用内联还是外部引入?
- 最好两者相结合
- 首屏急需的css和js内联进来
- 其他屏幕的可以使用外部引入方式
- 引用css和js的时机
- css越早引用越好:
- css放在header里
- 如果是最后引用,用户可能会看到无样式的html结构,影响体验
- js一般越晚引用越好,有时候也需要在一开始引用(header中)
- js放在body中,dom元素之后
- 如果是最早引用,js无法操控dom,或者js脚本执行太慢会影响dom的加载,用户会有可能体验到短暂的白屏
- 一般是越往后越好,但是像一些屏幕适配,ajax请求数据之类和操作dom无关的,可以放在前边(header中)
- css越早引用越好:
- 避免重复的资源请求
- 减小http请求资源的大小,用工具将css、js代码混淆,大小压缩,去除掉注释、空格、换行等生产无关的代码
- 减少http请求次数,合并请求资源数量,比如css、js合并,可以使用工具进行多个js或css合并,网上都有这些工具
- http缓存,一般是服务端设置
图片资源和其他的优化
着手点:
- 解决图片大的优化手段
- 解决图片多的优化手段
解决图片大的手段:
- 图片压缩处理,利用工具
- 使用更高压缩比格式的图片格式,例如webp
- 尽量少用图片:
- 使用图标字体带胎图片
- 利用css画图
解决图片多的手段:
- 合理使用base64内嵌图片,即转换成二进制图片
- 合并静态资源图片
- 雪碧图,比如有个导航图片很多个,可以将这些图片合并成一个很长的图片,然后用css的position和background进行选择使用
其他优化:
-
避免dom树套太多层级
-
避免空链接,类似这样:
// 不要这样写: <a href="">aaa</a> // 也不要这样写: <a href="###">aaa</a> // 而是要这样写: <a href="javascript:;">aaa</a> <img scr="img/blank.jpg" alt="">
-
避免使用慢元素:table和iframe
- table:table会把下面的tr和td读取完毕之后再渲染,而不是一行一行地渲染,所以避免使用,可以使用ul+li进行模拟
- iframe:会先读取iframe里面的所有元素之后,再加载iframe,所以也避免使用
-
主要内容写前边,次要内容写后边
- 网页加载是根据html资源从上往下加载的
- 例如网商页面,主要内容是商品区域,次要内容是导航栏和其他
- 这个时候就可以把商品区域的dom写在前边,导航栏和其他的结构写在后边
CSS选择器的优化
方法:
-
选择时候不要有无所谓的或者额外的修饰
-
尽量不要用ID进行选择,虽然ID选择速度比class快,但是不利于复用(此条不属于优化相关)
-
尽量保持层级简单,不要套用太多复杂的层级结构,而且class命名空间也一定要体验层级结构和业务归属,例如
demo-container-content
就可以一眼看出是demo标签下的东西,层级较少时候也容易定位不会产生误选,另外css层级解析是从右往左解析,而不是从左往右:// 分析:例如下方代码:css会先查找demo-container-content,然后向上一层查找demo-container,最后向上一层查找demo,如果三个都满足了,才会定位到这个元素 // html <div class="demo"> <div class="demo-container"> <div class="demo-container-content"></div> </div> </div> // css .demo .demo-container .demo-container-content{ width: 20px; }
-
避免使用通配符
*
-
删除掉空属性的选择器
-
避免使用正则选择器
-
提取公共部分尽量合并css选择器,减少css文件大小
-
合并属性,例如margin的四个属性合并,还是为了减少css文件大小
-
使用动画时候优先使用css3
-
尽量使用flex布局而不是float布局,float布局在移动端比较耗费性能
DOM操作优化
着手点:
- 加快dom操作的速度
- 减少dom操作的次数
优化方法:
- 尽量用id获取元素,id比class选择效率高
- 尽量从目标元素的父元素获取,什么意思呢,就是如果已经获取了目标元素的父元素,那么就不要再从
document.getElement
这里写了,而是写parentNode.getElement
- 尽量要使用变量缓存下来:如果多次进行相同节点的dom操作,就用变量缓存下来,对变量进行操作,减少dom获取次数
- 尽量不要再循环中进行dom操作,而是可以使用dom碎片临时变量存下来,然后循环后统一操作:
document.createDocumentFragment()
- 不要再循环中重复创建相同的node元素节点,即不要再循环中使用
document.createElement
,而是要在循环之前创建,然后在循环之中进行clone:ele.cloneNode(true)
(true表示clone元素内容,false为不克隆,一定要指定,因为各个浏览器的默认参数不一样) - 循环头中不要每次都引入element.length,这样也是会读取dom的,而是可以使用变量缓存下来:
for(var i=0, num = element.length; i< num; i++){}
- 修改样式时候尽量通过动态增删class,然后用css进行样式修改,尽量不用js修改style属性,因为js修改style会导致DOM渲染重排,重排成本高于重绘
DOM渲染:
- 重排:dom结构发生改变
- 重绘:节点样式改变
- 重绘成本低于重排
事件优化
事件委托:
- 使用原因:例如跑马灯里切换用的小圆点,如果每一个循环添加listener,就会降低js执行效率
- 做法:在他们的父容器中统一加入listener,只需要绑定一次,然后用冒泡方式获取点击,并且通过判断回调的event,进行相关判断
// 方法一:原生js
parent.addEventListener('click', function(ev){
if(根据ev判断是否点到了想要的元素){
执行相关代码
}
}, false)
// 方法二:jQuery/zepto库
$(parent).on('click', 'class或者其他选择器,或者其他判断条件', function(){
执行相关代码
})
事件节流/事件稀释:
- 使用原因:类似scroll、resize、touchmove、mousemove等瞬间会触发很多遍的回调操作,回调代码复杂的话会体验到十分明显的卡顿
- 做法:使用setTimeout进行节流,减少触发次数
var timer = null;
window.addEventListener('scroll', function(){
clearTimeout(timer);
timer = setTimeout(function(){
console.log('回调')
}, 20)
}, false)
资源按需加载(懒加载/延迟加载)、预加载
懒加载
- 当模块出现在屏幕的时候再加载
- 怎么判断是否出现在屏幕呢?可以使用元素的getBoundingClientRect()方法判断,getBoundingClientRect()方法说明:
- 方法会返回top、bottom、left、right四个值
- top:元素上边距到屏幕顶部的像素,在屏幕顶部上方时候为负,在屏幕顶部下方时候为正
- bottom:元素下边距到屏幕顶部的像素,在屏幕顶部上方时候为负,在屏幕顶部下方时候为正
- left:元素左边距到屏幕最左的像素,在屏幕最左的左边时候为负,在屏幕最左的右边为正
- right:元素右边距到屏幕最左的像素,在屏幕最左的左边时候为负,在屏幕最左的右边为正
- 那么判断是否出现在屏幕中就可以使用以下方法:
function isInScreen(el){ return el.bottom > 0 && el.top < window.innerHeight && el.left < window.innerWidth && el.right > 0; }
以懒加载图片为例,写一下最后懒加载的代码:
// 前提:先在图片img标签上定义class="lazyload-img-tag",并且图片所需要的真实src放在自定义属性:`data-src="xxx.jpg"`,真实的src可以先用loading图片占位
// 根据className得到所有需要懒加载的图片,这个是把伪数组转换为真数组,至于call和原型链之类的,以后再细说了
var imgArr = Array.prototype.slice.call(document.querySelectorAll(".lazyload-img-tag"));
lazyLoad();// 先加载首屏
// 滚动时候动态加载,可以考虑使用事件节流
window.addEventListener('scroll', function(){
lazyLoad();
}, false)
// 懒加载
function lazyLoad(){
for(var i = 0;i < imgArr.length; i++){
// 如果在屏幕范围内,就加载
if(isInScreen(imgArr[i])){
// 用先定义好的data-src属性中的地址替换上真实的src
imgArr[i].src = imgArr[i].getAttribute('data-src');
// 从数组中删掉,以后不用再加载一遍
imgArr.splice(i,1);
// 因为删掉了一个元素,i++已经执行,所以数组下标要减一,保证可以数组可以循环而且无遗漏
i = i - 1;
}
}
}
function isInScreen(el){
return el.bottom > 0 && el.top < window.innerHeight && el.left < window.innerWidth && el.right > 0;
}