网页性能优化

来源:互联网 发布:淘宝seo搜索优化 编辑:程序博客网 时间:2024/06/06 16:47

      作为一名前端开发者,你写的网页经常卡顿吗?即使页面逻辑不复杂,DOM树也不复杂,但是感脚页面还是不够流畅,不够给力,那么本文就带领大家一步一步进行网页性能优化。

     首先,为什么人的视觉感觉到卡?正常情况下,动画每秒要有24帧,低于24帧人眼就会感觉到停顿的感觉。一般的网页动画,需要达到每秒30帧到60帧的频率,才能比较流畅。也就是说网页的刷新率要在30f'ps-60fps之间,FPS(frame per second)!换言之,如果想达到60帧美妙,就意味着JavaScript线程每个任务的耗时,必须少于16毫秒。由于,javascript是单线程的,也就是同一时刻要么进行UI渲染要么进行js计算,所以如果你的UI渲染时间太长、或者js计算太耗时,都会引起你的网页的刷新率变低,如果低于24fps,那就特别卡顿了!!!

   搞清楚网页卡顿的可能的原因之后,那我们打开chrome的timeline工具,来找出是网页卡顿的罪魁祸首吧!

   

   如果你的chrome找不到timeline,就在performance里面recorde。上面的时间轴上可以清楚的看出CPU的占用情况,其中蓝色代表网络与html解析,黄色代表js脚本运行时间,紫色代表DOM结构的重新布局,绿色代表绘制。通过右边的饼状图,你可以清楚的知道在某个时间段内,cpu是怎样被瓜分的。

   如果,你发现你的js脚本运行太耗时引起的页面卡顿时,你有又两种解决办法。策略一:由于js是单线程的,UI的渲染时当线程空闲时才会渲染,也就是说UI的渲染是在异步环境里面的,只有当同步任务执行完了才会执行异步任务,(如果你还不了解js的同步与异步 请参考http://www.ruanyifeng.com/blog/2014/10/event-loop.html)所以如果你的js计算太耗时,主执行线程就无法留时间给UI渲染,所以你应当把耗时较长的js脚本也扔到异步环境里,让cpu腾出时间先进行UI渲染。策略二:使用Web Worker,主线程只用于UI渲染,然后跟UI渲染不相干的任务,都放在Worker线程。因为,当你复杂的js计算在主线程运行的时候,页面是属于卡死无响应状态,所以耗时的js计算应当用别的线程,当耗时任务运行完毕通过线程间消息机制通知主线程。

   所谓无图无真相,下面我将写个例子。首先上个效果图:

   

看上图,其实我的意图很简单,当我点击‘点击计算’按钮时,首先会显示一个‘正在计算’的提示,然后进行耗时运算,当运算结束后,提示变为‘计算完毕’!然而第一个按钮并没有达到我的预期,‘正在计算’提示似乎没有出现。。这个例子我只想证明耗时js计算是真的会堵塞UI渲染的!!!而后面两个按钮的例子是我用上述两种策略优化过的,最终达到我的预期效果。

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>你的网页为什么会很卡</title></head><body>    <button onclick="caluate1()">点击计算</button><h6 id="demo1"></h6>    <button onclick="caluate2()">点击计算</button><h6 id="demo2"></h6>    <button onclick="caluate3()">点击计算</button><h6 id="demo3"></h6>    <!--浏览器同一时候只能干一件事,要么UI渲染,要么运行js-->    <!--请对比一下两个实例-->    <script>        function caluate1() {            document.getElementById('demo1').innerText = '正在计算.....';            for(let i = 0;i<1000;i++){                for(let j = 0;j<1000;j++){                    for(let z = 0;z<1000;z++){                    }                }            }            document.getElementById('demo1').innerText = '计算完毕';        }        function caluate2() {            document.getElementById('demo2').innerText = '正在计算.....';            setTimeout(function () {                for(let i = 0;i<1000;i++){                    for(let j = 0;j<1000;j++){                        for(let z = 0;z<1000;z++){                        }                    }                }                document.getElementById('demo2').innerText = '计算完毕';            },0);        }//        所以解决js计算时间过长,堵塞ui渲染的解决办法之一就是将耗时任务丢到异步环境。解决办法之二就是用webworker多线程       function caluate3(){           document.getElementById("demo3").innerHTML='正在计算。。';           w=new Worker("caculate_worker.js");           w.onmessage=function(event){               document.getElementById("demo3").innerHTML='计算完毕';           };        }//        一般来说javascript和页面的UI会共用一个线程,所以当点击一个按钮开始运行Javascript后,在这段代码运行完毕之前,页面是无法响应用户操作的,换句话来说就是被“冻结”了。而这段代码可以交给Web Worker在后台运行,那么页面在Javascript运行期间依然可以响应用户操作。后台会启动一个worker线程来执行这段代码,用户可以创建多个worker线程    </script></body></html>
         在三个例子中,我都用三重循环模拟同步耗时运算,在caculate1函数中,我先将demo1的文本变成‘正在计算’(①任务 UI渲染),然后执行三重循环(②任务 js计算),最后将文本变回‘计算完毕’(③任务  UI渲染),结果却只出现了‘计算完毕’,谁能告诉我这三个任务的执行顺序,答案是②①③,所以你根本看不到‘正在计算’的提示。答案还是因为UI渲染时异步的,而耗时的js计算是同步执行的。而在第二个例子中,我用了个定时器,0秒后执行,其实只是想把三重循环丢到异步事件队列里面去,这样执行的顺寻就变成了①②③,达到了我想要的效果。在第三个例子中我用的是webworker,后台线程执行三重循环,不会堵塞主线程的UI渲染。

       上面说了一大堆,其实也就说了一种请求,就是由于我们的js脚本耗时执行堵塞了页面渲染,导致页面卡顿。下面还有一种情况,就是UI渲染耗时。

       首先,我们得明白当我们用js去操作DOM,是可能会触发浏览器重排(reflow)和重绘的(repaint),比如你修改某个元素的位置会触发reflow,修改背景颜色会触发repaint,当你要获取某个元素的offsetLeft等位置信息,也是会触发浏览器重排的。所以,如果你频繁的让浏览器重新渲染(包括reflow和repaint),对网页的性能是极其不利的。所以,减少网页的频繁渲染是提高网页性能的关键。

   下面几个技巧可以让你尽可能的少触发浏览器的(reflow repaint)

   1.读写分离

    var top = dom.offsetTop;

    dom.style.top= top+10+"px"; 

    var left= dom.offsetLeft;

    dom.style.left= left+10+"px";          

          以上是不好的写法,应改成如下:

         var top = dom.offsetTop;

   var left= dom.offsetLeft;

   dom.style.top= top+10+"px";

   dom.style.left= left+10+"px";  

  2.尽量不要逐条改变样式,用class一次性修改

  3.使用fragament文档碎片的形式,fragament相当于一个容器,你可以把真实的DOM劫持到fragament里面来,然后再对fragament里的节点肆意妄为地进行读写操作,操作完毕后,将fragment添加到真实DOM树中,原理很简单,就是fragament相当于是离线DOM,它在内存进行读写,不会引起真实DOM树的重新渲染,如果你研究过vue的源码,其实vue在进行解析模板之前就是先把它转化为fragament,完事后才加到页面上。这就是为了优化性能。

  4.如果你要对一个DOM进行一百次操作,你可以先把该DOMdisplay设置为none,然后再对其操作100次,然后再把display设置为block,原因和上面一条类似,因为对dipaly为none的元素读写的不会触发reflow。

  5.如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。

    6.使用虚拟DOM,比如vue react 在你页面很复杂,他们可以保证你仍然拥有不错的性能

 其实,还有一点,上面没讲到,也是能提高我们网页性能的,就是css的优化,css越复杂,选择器层级越多,用到的box-shaow,border-raduis这种高消耗的样式属性越多,多余的css样式没被删除,滥用reset都是会增大浏览器repaint的成本的,所以你可以优化你的css选择器,减少css层级关系,开启GPU硬件加速,合理使用reset,都是会对你的网页进行一个优化的。

    当然停留在理论没用,你需要在实际项目中进行体会,每一个小的细节优化了,对整个页面将会是一个比较大的优化,在这个重用户体验的时代,所以,你做的网页还能卡顿吗?