视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Js前端性能优化总结
2020-11-27 20:03:08 责编:小采
文档


如果使用前端打包工具,可以在打包文件时候在给文件添加版本号或者hash值,同样可以区分资源是否过期。

减少http请求

  • 使用CDN托管静态资源

  • 可以借助gulp、webpack等打包工具对js、css等文件合并与压缩

  • 图片懒加载、按需加载,当滚动到图片可视区域才去加载图片

  • 小图片并且基本不会改动的图片使用base编码传输。base不要滥用,即使小图片经过base编码之后也会生成很长的字符串,如果滥用base反而会适得其反

  • 雪碧图,这个也是针对基本不会更改的图片才使用雪碧图,因为如果一张图片修改,会导致整个雪碧图重新生成,如果乱用也会适得其反。

  • 减小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('p')获取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的两倍。

    下载本文
    显示全文
    专题