Js基础知识(五) - 前端性能优化总结

前端性能优化总结

资源优化

缓存

最好的资源优化就是不加载资源。缓存也是最见效的优化手段。说实话,虽说客户端缓存发生在浏览器端,但缓存主要仍是服务端来控制,与咱们前端关系并非很大。但仍是有必要了解一下。javascript

缓存包括服务端缓存和客户端缓存,本文只谈客户端缓存。所谓客户端缓存主要是http缓存。http缓存主要分为强制缓存和协商缓存。css

强制缓存

  • Expires(http1.0)

在http1.0中使用Expires来作强制缓存。Exprires的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但因为服务端时间和客户端时间可能有偏差,这也将致使缓存命中的偏差。html

  • Cache-Control

Cache-Control有不少属性,不一样的属性表明的意义也不一样。前端

  1. private:客户端能够缓存
  2. public:客户端和代理服务器均可以缓存
  3. max-age=t:缓存内容将在t秒后失效
  4. no-cache:须要使用协商缓存来验证缓存数据
  5. no-store:全部内容都不会缓存。

协商缓存

浏览器第一次请求数据时,服务器会将缓存标识与数据一块儿响应给客户端,客户端将它们备份至缓存中。再次请求时,客户端会将缓存中的标识发送给服务器,服务器根据此标识判断。若未失效,返回304状态码,浏览器拿到此状态码就能够直接使用缓存数据了。java

  • Last-Modified

服务器在响应请求时,会告诉浏览器资源的最后修改时间webpack

  • if-Modified-Since

浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中得到的Last-Modified(最后修改时间)。服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,若是大于被请求资源最后修改时间则返回304,浏览器从缓存获取资源。若是小于被请求资源最后修改时间,则返回200,并返回最新资源,浏览器从服务端获取最新的资源,并缓存。css3

  • Etag

由服务器生成的每一个资源的惟一标识字符串web

  • If-None-Match

再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现If-None-Match则与被请求资源的惟一标识进行对比。若是相同,说明资源没有被修改过,返回304,浏览器从缓存获取资源,若是不一样说明资源被修改过,则返回200,并返回最新资源,浏览器从服务端获取最新资源,并缓存。chrome

Last-Modified与ETag是能够一块儿使用的,服务器会优先验证ETag,一致的状况下,才会继续比对Last-Modified,最后才决定是否返回304。

若是使用前端打包工具,能够在打包文件时候在给文件添加版本号或者hash值,一样能够区分资源是否过时。gulp

减小http请求

  • 使用CDN托管静态资源
  • 能够借助gulp、webpack等打包工具对js、css等文件合并与压缩
  • 图片懒加载、按需加载,当滚动到图片可视区域才去加载图片
  • 小图片而且基本不会改动的图片使用base64编码传输。base64不要滥用,即便小图片通过base64编码以后也会生成很长的字符串,若是滥用base64反而会拔苗助长
  • 雪碧图,这个也是针对基本不会更改的图片才使用雪碧图,由于若是一张图片修改,会致使整个雪碧图从新生成,若是乱用也会拔苗助长。

减少http请求资源体积

  • 借助webpack、gulp等工具压缩资源
  • 服务端开启gzip压缩(压缩率很是可观,通常都在30%之上)
  • 若是有用打包工具,打包优化要作好,公共资源、提取第三方代码、不须要打包的库...

渲染优化

读过前面js运行机制的应该知道,从浏览器输入url,到页面出如今屏幕上,都发生了哪些事(tcp握手、dns解析等不在认知范围)。
  • FPS 16ms 小于10ms完成最好 Google devtool 查看帧率

若是浏览器FPS到达60,就会显得比较流畅,大多数显示器的刷新频率是60Hz,浏览器会自动按照这个频率刷新动画。
按照FPS等于60来计算,平均一帧的时间为1000ms/60 = 16.7ms,因此每次渲染时间不能超过16ms,若是超过这个时间就会出现丢帧、卡顿现象。

能够在chrome浏览器开发者工具中的Timeline中查看刷新率,能够查看全部帧率耗时状况以及某一帧的执行状况。Timeline的使用教程:https://segmentfault.com/a/11...

为了保证正常的FPS,有些渲染性能优化仍是有必要的。下面所介绍的都是有关渲染优化的策略。

  • 尽可能使用css3来作动画

总所周知,css的性能要比js快,因此能使用css,尽可能不用js来实现

  • 避免使用setTimeout或setInterval,尽可能使用requestAnimationFrame来作动画或者高频Dom操做。

由于setTimeout和setInterval没法保证callback函数的执行时机,极可能在帧结束的时候执行,从而致使丢帧,可是requestAnimationFrame能够保证callback函数在每帧动画开始的时候执行
requestAnimationFrame的中文MDN地址:https://developer.mozilla.org...

  • 复杂的计算操做使用Web Workers

若是有须要复杂的数据操做,好比对一个有一个个元素的数组遍历求和,那么Web Workers在适合不过了。

Web Workers可让JavaScript脚本运行在后台线程(相似于建立一个子线程),然后台线程不会影响到主线程中的页面。不过,使用Web Workers建立的线程是不能操做DOM树。
有关Web Workers的更多能够查看MDN详解:https://developer.mozilla.org...

  • css放在头部,js放在尾部。

读过前面js运行机制的应该知道页面渲染是怎样一个过程,再也不赘述了。css放在头部会避免生成html树以后从新布局的闪屏现象,js通常对页面的影响较大,通常放在尾部最后执行。

  • 事件防抖(debounce)与节流(throttle)

针对高频触发的事件(mousemove、scroll)等事件,若是不加以控制会在短期内触发不少事件。

函数防抖是指频繁触发的状况下,只有足够的空闲时间,才执行代码一次。场景:注册时邮箱的输入框,随着用户的输入,实时判断邮箱格式是否正确,当第一次输入事件触发,设置定时:在800ms以后执行检查。假如只过了100ms,上次的定时还没执行,此时清除定时,从新定时800ms。直到最近一次的输入,后面没有紧邻的输入了,这最近一次的输入定时计时结束,终于执行了检查代码。

const filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;  
$("#email").on("keyup",checkEmail());  
function checkEmail(){  
    let timer=null;  
    return function (){  
        clearTimeout(timer);  
        timer=setTimeout(function(){  
            console.log('执行检查');  
        },800);  
    }  
}

函数节流是指必定时间内js方法只跑一次。就是原本一秒要执行100次的变成一秒执行10次。
场景:函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。

var canRun = true;
document.getElementById("throttle").onscroll = function(){
    if(!canRun){
        // 判断是否已空闲,若是在执行中,则直接return
        return;
    }

    canRun = false;
    setTimeout(function(){
        console.log("函数节流");
        canRun = true;
    }, 300);
};
  • Dom操做

前端开发人员都知道Do操做是很是耗时的(曾亲测过30*30的表格遍历添加样式)。因此尽可能避免频繁的Dom操做,若是避免不了就尽可能对DOm操做作优化。

1.:缓存Dom查询,好比经过getElementByTagName('div')获取Dom集,而不是逐个获取。

2: 合并Dom操做,使用createDocumentFragment()
var frag = document.createDocumentFragment()
    for (i<10) {
        var li = document.createElement('li')
        frag.appendChild(li)
    }
    document.body.appendChild(frag)
3: 使用React、Vue等框架的虚拟dom(原理目前还不明白),能够更快的实现dom操做。
  • 尽可能避免重绘(rePaint)和回流(reFlow)

若是使用js修改元素的颜色或者背景色就会触发重绘,重绘的开销仍是比较昂贵的,由于浏览器会在某一个DOM元素的视觉效果改变后去check这个DOM元素内的全部节点。

若是修改元素的尺寸和位置就会发生回流,回流开销更大,它会在某一个DOM元素的位置发生改变后触发,并且它会从新计算全部元素的位置和在页面中的占有的面积,这样的话将会引发页面某一个部分甚至整个页面的从新渲染。

  • css3硬件加速

浏览器渲染时,会分为两个图层:普通图层和复合图层。

普通文档流内能够理解为一个复合图层,absolute、fixed布局虽然能够脱离普通文档流,但它仍然属于普通图层,不会启动硬件加速。上面说的重绘(rePaint)和回流(reFlow)说的就是普通图层上的重绘和回流。

复合图层会启动硬件加速。和普通图层不在同一个图层,因此复合图层不会影响普通图层,若是一个元素被提高到复合图层,再操做该元素时,就不会引发普通图层的重绘和回流,从而提高渲染性能。

如何启动硬件加速:

1.使用translate3d和translateZ

webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);

webkit-transform: translate3d(0,0,0);
-moz-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
-o-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);

2.使用opacity
须要动画执行的过程当中才会建立合成层,动画没有开始或结束后元素还会回到以前的状态

3.使用will-chang属性
这个属性比较不经常使用,通常配合opacity与translate使用

针对webkit浏览器,启用硬件加速有些时候可能会致使浏览器频繁闪烁或抖动,可使用下面方法消除:

-webkit-backface-visibility:hidden;
-webkit-perspective:1000;
若是使用硬件加速,请使用z-index配合使用, 由于若是这个元素添加了硬件加速,而且index层级比较低, 那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,而且releative或absolute属性相同的), 会默认变为复合层渲染,若是处理不当会极大的影响性能
  • 避免强制同步布局和布局抖动

浏览器渲染过程为:js/css(javascript) > 计算样式(style) > 布局(layout) > 绘制(paint) > 渲染合并图层(Composite)

JavaScript:JavaScript实现动画效果,DOM元素操做等。
Style(计算样式):肯定每一个DOM元素应该应用什么CSS规则。
Layout(布局):计算每一个DOM元素在最终屏幕上显示的大小和位置。
Paint(绘制):在多个层上绘制DOM元素的的文字、颜色、图像、边框和阴影等。
Composite(渲染层合并):按照合理的顺序合并图层而后显示到屏幕上。

在js中若是读取style属性的某些值就会让浏览器强行进行一次布局、计算,而后再返回值,好比:

offsetTop, offsetLeft, offsetWidth, offsetHeight

scrollTop/Left/Width/Height

clientTop/Left/Width/Height

width,height

请求了getComputedStyle(), 或者 IE的 currentStyle

因此,若是强制浏览器在执行JavaScript脚本以前先执行布局过程,这就是所谓的强制同步布局。
好比下面代码:

requestAnimationFrame(logBoxHeight);

// 先写后读,触发强制布局
function logBoxHeight() {
    // 更新box样式
    box.classList.add('super-big');

    // 为了返回box的offersetHeight值
    // 浏览器必须先应用属性修改,接着执行布局过程
    console.log(box.offsetHeight);
}

// 先读后写,避免强制布局
function logBoxHeight() {
    // 获取box.offsetHeight
    console.log(box.offsetHeight);

    // 更新box样式
    box.classList.add('super-big');
}

在JavaScript脚本运行的时候,它能获取到的元素样式属性值都是上一帧画面的,都是旧的值。所以,若是你在当前帧获取属性以前又对元素节点有改动,那就会致使浏览器必须先应用属性修改,结果执行布局过程,最后再执行JavaScript逻辑。

若是连续屡次强制同步布局,就会致使布局抖动
好比下面代码:

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
  }
}

做者:SylvanasSun
连接:https://juejin.im/post/59da456951882525ed2b706d
来源:掘金
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

咱们知道浏览器是一帧一帧的刷新页面的,对于每一帧,上一帧的布局信息都是已知的。
强制布局就是使用js强制浏览器提早布局,好比下面代码:

// bed  每次循环都要去获取left ,就会发生一次回流
function logBoxHeight() {
  box.style.left += 10
  console.log(box.style.left)
}

// goog 
var width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = width + 'px';
  }
}
  • DOMContentLoaded与Load

DOMContentLoaded 事件触发时,仅当DOM加载完成才触发DOMContentLoaded,此时样式表,图片,外部引入资源都还没加载。而load是等全部的资源加载完毕才会触发。

1. 解析HTML结构。
2. 加载外部脚本和样式表文件。
3. 解析并执行脚本代码。
4. DOM树构建完成。//DOMContentLoaded
5. 加载图片等外部文件。
页面加载完毕。//load
  • 视觉优化

等待加载时间能够合理使用loading gif动图必定程度上消除用户等待时间的烦躁感

代码性能

代码对性能的影响可大可小,可是养成一个良好的写代码习惯和高质量的代码,会潜移默化的提升性能,同时也能提升本身的水平。废话很少说,直接看我总结的部分要点(由于这一部分知识点太多,须要你们写代码的时候多多总结)。

  • 避免全局查找

访问局部变量会比访问全局变量快,由于js查找变量的时候如今局部做用局查找,找不到在逐级向上找。

// bad
function f () {
    for (...){
        console.log(window.location.href)
    }
}

//good
function f () {
    var href = window.location.href
    for (...){
        console.log(href)
    }
}
  • 循环技巧
// bed 
for(var i = 0; i < array.length; i++){
    ....
}
// good
for(var i = 0, len = array.length; i < len; i++){
    ....
}
// 不用每次查询长度
  • 不要使用for in 遍历数组

for in是最慢的,其余的都差很少,其中直接使用for循环是最快的。for in只是适合用来遍历对象。

  • 使用+''代替String()吧变量转化为字符串
var a = 12
//bad
a = String(a)

// good
var a = 12
a = a + ''

这个还有不少相似的,好比使用*1代替parseInt()等都是利用js的弱类型,其实这样对性能提高不是很大,网上有人测试过,进行十几万次变量转换,才快了零点几秒。

  • 删除dom

删除dom元素要删除注册在该节点上的事件,不然就会产生没法回收的内存,在选择removeChild和innerHTML=''两者之间尽可能选择后者,听说removeChild有时候没法有效的释放节点(具体缘由不明)

  • 使用事件代理处理事件

任何能够冒泡的事件均可以在节点的祖先节点上处理,这样对于子节点须要绑定相同事件的状况就不用分别给每一个子节点添加事件监听,而是都提高到祖先节点处理。

  • 经过js生成的dom对象必须append到页面中

在IE下,js建立的额dom若是没有添加到页面,这部份内存是不会被回收的

  • 避免与null比较

可使用下面方法替换与null比较
1.若是该值为引用类型,则使用instanceof检查其构造函数
2.若是该值为基本类型,使用typeof检查类型

  • 尽可能使用三目运算符代替if else
if(a>b){num = a}
else{num = b}

// 能够替换为
num = a > b ? a : b
  • 当判断条件大于3中状况时,使用switch代替if

由于switch的执行速度比if要快,也别是在IE下,速度大约是if的两倍

先总结这么多,其实性能优化还有不少,像预加载、服务端渲染、css选择器优化等等。等有机会再总结
相关文章
相关标签/搜索