这段时间对项目作了一次总体的优化,全站有了20%左右的提高(原本载入速度已经1.2S左右了,优化度很低),算一算已经作了四轮的全站性能优化了,回顾几回的优化手段,基本上几个字就能说清楚:javascript
传输层面:减小请求数,下降请求量
执行层面:减小重绘&回流
传输层面的历来都是优化的核心点,而这个层面的优化要对浏览器有一个基本的认识,好比:css
① 网页自上而下的解析渲染,边解析边渲染,页面内CSS文件会阻塞渲染,异步CSS文件会致使回流html
② 浏览器在document下载结束会检测静态资源,新开线程下载(有并发上限),在带宽限制的条件下,无序并发会致使主资源速度降低,从而影响首屏渲染前端
③ 浏览器缓存可用时会使用缓存资源,这个时候能够避免请求体的传输,对性能有极大提升java
衡量性能的重要指标为首屏载入速度(指页面能够看见,不必定可交互),影响首屏的最大因素为请求,因此请求是页面真正的杀手,通常来讲咱们会作这些优化:webpack
① 合并样式、脚本文件git
② 合并背景图片github
③ CSS3图标、Icon Fontweb
① 开启GZipajax
② 优化静态资源,jQuery->Zepto、阉割IScroll、去除冗余代码
③ 图片无损压缩
④ 图片延迟加载
⑤ 减小Cookie携带
不少时候,咱们也会采用相似“时间换空间、空间换时间”的作法,好比:
① 缓存为王,对更新较缓慢的资源&接口作缓存(浏览器缓存、localsorage、application cache这个坑多)
② 按需加载,先加载主要资源,其他资源延迟加载,对非首屏资源滚动加载
③ fake页技术,将页面最初须要显示Html&Css内联,在页面所需资源加载结束前至少可看,理想状况是index.html下载结束即展现(2G 5S内)
④ CDN
......
从工程的角度来看,上述优化点半数以上是重复的,通常在发布时候就直接使用项目构建工具作掉了,还有一些只是简单的服务器配置,开发时不须要关注。
能够看到,咱们所作的优化都是在减小请求数,下降请求量,减少传输时的耗时,或者经过一个策略,优先加载首屏渲染所需资源,然后再加载交互所需资源(好比点击时候再加载UI组件),Hybrid APP这方面应该尽量多的将公共静态资源放在native中,好比第三方库,框架,UI甚至城市列表这种经常使用业务数据。
有一些网站初期比较快,可是随着量的积累,BUG愈来愈多,速度也愈来愈慢,一些前端会使用上述优化手段作优化,可是收效甚微,一个比较典型的例子就是代码冗余:
① 以前的CSS所有放在了一个文件中,新一轮的UI样式优化,新老CSS难以拆分,CSS体量会增长,若是有业务团队使用了公共样式,状况更不容乐观;
② UI组件更新,可是若是有业务团队脱离接口操做了组件DOM,将致使新组件DOM更新受限,最差的状况下,用户会加载两个组件的代码;
③ 胡乱使用第三方库、组件,致使页面加载大量无用代码;
......
以上问题会不一样程度的增长资源下载体量,若是听之任之会产生一系列工程问题:
① 页面关系错综复杂,需求迭代容易出BUG;
② 框架每次升级都会致使额外的请求量,常加载一些业务不须要的代码;
③ 第三方库泛滥,且难以维护,有BUG也改不了;
④ 业务代码加载大量异步模块资源,页面请求数增多;
......
为求快速占领市场,业务开发时间每每紧迫,使用框架级的HTML&CSS、绕过CSS Sprite使用背景图片、引入第三方工具库或者UI,会常常发生。当遇到性能瓶颈时,若是不从根源解决问题,用传统的优化手段作页面级别的优化,会出现很快页面又被玩坏的状况,几回优化结束后我也在思考一个问题:
前端每次性能优化的手段皆大同小异;代码的可维护性也基本是在细分职责; 既然每次优化的目的是相同的,每次实现的过程是类似的,而每次从新开发项目又基本是要重蹈覆辙的,那么工程化、自动化多是这一切问题的最终答案
工程问题在项目积累到必定量后可能会发生,通常来讲会有几个现象预示着工程问题出现了:
① 代码编写&调试困难
② 业务代码很差维护
③ 网站性能广泛很差
④ 性能问题重复出现,而且有不可修复之势
像上面所描述状况,就是一个典型的工程问题;定位问题、发现问题、解决问题是咱们处理问题的手段;而如何防止同一类型的问题重复发生,即是工程化须要作的事情,简单说来,优化是解决问题,工程化是避免问题,今天咱们就站在工程化的角度来解决一些前端优化问题,防止其死灰复燃。
文中是我我的的一些开发经验,但愿对各位有用,也但愿各位多多支持讨论,指出文中不足以及提出您的一些建议。
咱们这里作的第一个事情即是消除优化路上第一个拦路虎:代码冗余(作代码精简),单从一个页面的加载来讲,他须要如下资源:
① 框架MVC骨架模块&框架级别CSS
② UI组件(header组件、日历、弹出层、消息框......)
③ 业务HTML骨架
④ 业务CSS
⑤ 业务Javascript代码
⑥ 服务接口服务
由于产品&视觉会常常折腾全站样式加之UI的灵活性,UI最容易产生冗余的模块。
UI组件自己包括完整的HTML&CSS&Javascript,一个复杂的组件下载量能够达到10K以上,就UI部分来讲容易致使两个工程化问题:
① 升级产生代码冗余
② 对外接口变化致使业务升级须要额外开发
最理想的升级是保持对外的接口不变甚至保持DOM结构不变,但多数状况的UI升级实际上是UI重作,最坏的状况是不作老接口兼容,这个时候业务同事便须要修改代码。为了防止业务抱怨,UI制做者每每会保留两个组件(UI+UI1),若是原来那个UI是核心依赖组件(好比是UIHeader组件),便会直接打包至核心框架包中,这时便出现了新老组件共存的局面,这种状况是必须避免的,UI升级须要遵照两个原则:
① 核心依赖组件必须保持单一,相同功能的核心组件只能有一个
② 组件升级必须作接口兼容,新的特性能够作加法,毫不容许对接口作减法
项目之初,分层较好的团队会有一个公共的CSS文件(main.css),一个业务CSS文件,main.css包含公共的CSS,而且会包含全部的UI的样式:
半年后业务频道增,UI组件需求一多便容易膨胀,弊端立刻便暴露出来了,最初main.css可能只有10K,可是不出半年就会膨胀至100K,而每一个业务频道一开始便须要加载这100K的样式文件页面,可是其中多数的UI样式是首屏加载用不到的。
因此比较好的作法是,main.css只包含最核心的样式,理想状况是什么业务样式功能皆不要提供,各个UI组件的样式打包至UI中按需加载:
如此UI拆分后,main.css老是处于最基础的样式部分,而UI使用时按需加载,就算出现两个相同组件也不会致使多下载资源。
一个PC业务页面,其模块是很复杂的,这个时候能够将之分为多个模块:
一经拆分后,页面即是由业务组件组成,只须要关注各个业务组件的开发,而后在主控制器中组装业务组件,这样主控制器对页面的控制力度会增长。
业务组件通常重用性较低,会产生模块间的业务耦合,还会对业务数据产生依赖,可是主体仍然是HTML&CSS&Javascript,这部分代码也是常常致使冗余的,若是能按模块拆分,能够很好的控制这一问题发生:
按照上述的作法如今的加载规则是:
① 公共样式文件
② 框架文件,业务入口文件
③ 入口文件,异步加载业务模块,模块内再异步加载其它资源
这样下来业务开发时便不须要引用样式文件,能够最大限度的提高首屏载入速度;须要关注的一点是,当异步拉取模块时,内部的CSS加载须要一个规则避免对其它模块的影响,由于模块都带有样式属性,页面回流、页面闪烁问题须要关注。
一个实际的例子是,这里点击出发后的城市列表即是一个完整的业务组件,城市选择的资源是在点击后才会发生请求,而业务组件内部又会细分小模块,再细分的资源控制由实际业务状况决定,过于细分也会致使理解和代码编写难度上升:
若是哪天需求方须要用新的城市选择组件,即可以直接从新开发,让业务之间使用最新的城市列表便可,由于是独立的资源,因此老的也是可使用的。
只要能作到UI级别的拆分与页面业务组件的拆分,便能很好的应付样式升级的需求,这方面冗余只要能避过,其它冗余问题便不是问题了,有两个规范最好遵照:
1 避免使用全局的业务类样式,就算他建议你使用 2 避免不经过接口直接操做DOM
冗余是首屏载入速度最大的拦路虎,是历史造成的包袱,只要能消除冗余,便能在后面的路走的更顺畅,这种组件化编程的方法也能让网站后续的维护更加简单。
解决冗余便抛开了历史的包袱,是前端优化的第一步也是比较难的一步,但模块拆分也将全站分红了不少小的模块,载入的资源分散会增长请求数;若是所有合并,会致使首屏加载不须要的资源,也会致使下一个页面不能使用缓存,如何作出合理的入口资源加载规则,如何合理的善用缓存,是前端优化的第二步。
通过几回性能优化对比,得出了一个较优的首屏资源加载方案:
① 核心框架层:mvc骨架、异步模块加载器(require&seajs)、工具库(zepto、underscore、延迟加载)、数据请求模块、核心依赖UI(header组件消息类组件)
② 业务公共模块:入口文件(require配置,初始化工做、业务公共模块)
③ 独立的page.js资源(包含template、css),会按需加载独立UI资源
④ 全局css资源
这里若是追求极致,libs.js、main.css与main.js能够选择合并,划分结束后即可以决定静态资源缓存策略了。
资源缓存是为二次请求加速,比较经常使用的缓存技术有:
① 浏览器缓存
② localstorage缓存
③ application缓存
application缓存更新一块很差把握容易出问题,因此更多的是依赖浏览器以及localstorage,首先说下浏览器级别的缓存。
只要服务器配置,浏览器自己便具备缓存机制,若是要使用浏览器机制做缓存,势必关心一个什么时候更新资源问题,咱们通常是这样作的:
<script type="text/javascript" src="libs.js?t=20151025"></script>
这样作要求必须先发布js文件,才能发布html文件,不然读不到最新静态文件的。一个比较尴尬的场景是libs.js是框架团队甚至第三方公司维护的,和业务团队的index.html是两个团队,互相的发布是没有关联的,因此这二者的发布顺序是不能保证的,因而转向开始使用了MD5的方式。
为了解决以上问题咱们开始使用md5码的方式为静态资源命名:
<script type="text/javascript" src="libs_md5_1234.js"></script>
每次框架更新便不作文件覆盖,直接生成一个惟一的文件名作增量发布,这个时候若是框架先发布,待业务发布时便已经存在了最新的代码;当业务先发布框架没有新的时,便继续沿用老的文件,一切都很美好,虽然业务开发偶尔会抱怨每次都要向框架拿MD5映射,直到框架一次BUG发生。
忽然一天框架发现一个全局性BUG,而且立刻作出了修复,业务团队也立刻发布上线,但这种事情出现第二次、第三次框架这边便压力大了,这个时候框架层面但愿业务只须要引用一个不带缓存的seed.js,seed.js要怎么加载是他本身的事情:
<script type="text/javascript" src="seed.js"></script>
//seed.js须要按需加载的资源 <script src="libs_md5.js"></script> <script src="main_md5.js"></script>
固然,因为js加载是顺序是不可控的,咱们须要为seed.js实现一个最简单的顺序加载模块,映射什么的由构建工具完成,每次作覆盖发布便可,这样作的缺点是额外增长一个seed.js的文件,而且要承担模块加载代码的下载量。
也会有团队将静态资源缓存至localstorage中,以期作离线应用,可是我通常用它存json数据,没有作过静态资源的存储,想要尝试的朋友必定要作好资源更新的策略,而后localstorage的读写也有必定损耗,不支持的状况还须要作降级处理,这里便很少介绍。
若是是Hybrid的话,状况有所不一样,须要将公共资源打包至native中,业务类就不要打包了,不然native会愈来愈大。
以前与淘宝的一些朋友作过交流,发现他们竟然作到了零散资源在服务器端作合并的地步了......这方面咱们仍是望洋兴叹吧
所谓工程化,能够简单认为是将框架的职责拓宽再拓宽,主旨是帮业务团队更好的完成需求,工程化会预测一些常碰到的问题,将之扼杀在摇篮,而这种路径是可重用的,是具备可持续性的,好比第一个优化去除冗余,是在屡次去除冗余代码,思考冗余出现的缘由而最终思考得出的一个避免冗余的方案,前端工程化须要考虑如下问题:
重复工做;如通用的流程控制机制,可扩展的UI组件、灵活的工具方法 重复优化;如下降框架层面升级带给业务团队的耗损、帮助业务在无感知状况下作掉大部分优化(好比打包压缩什么的) 开发效率;如帮助业务团队写可维护的代码、让业务团队方便的调试代码(好比Hybrid调试)
要完成前端工程化,少不了工程化工具,requireJS与grunt的出现,改变了业界前端代码的编写习惯,同时他们也是推进前端工程化的一个基础。
requireJS是一伟大的模块加载器,他的出现让javascript制做多人维护的大型项目变成了事实;grunt是一款javascript构建工具,主要完成压缩、合并、图片压缩合并等一系列工做,后续又出了yeoman、Gulp、webpack等构建工具。
这里这里要记住一件事情,咱们的目的是完成前端工程化,不管什么模块加载器或者构建工具,都是为了帮助咱们完成目的,工具不重要,目的与思想才重要,因此在完成工程化前讨论哪一个加载器好,哪一种构建工具好是舍本逐末的。
如今站在前端优化的角度,如下面这个页面为例,最优的载入状况是什么呢:
以这个看似简单页面来讲,若是要完整的展现涉及的模块比较多:
① 框架MVC骨架模块&框架级别CSS
② 几个UI组件(header组件、日历、弹出层、消息框......)
③ 业务HTML骨架
④ 业务CSS
⑤ 业务Javascript代码
⑥ 服务接口服务
上面的不少资源事实上对于首屏渲染是没有帮助的,根据以前的探讨,得出的理想首屏加载所需资源是:
① 框架MVC骨架&框架级别CSS => main.css+libs.js
② 业务入口文件 => main.js
③ 业务交互控制器 => page.js
有了这些资源,便能完成完整的交互,包括接口请求,列表展现,但如果只须要给用户“看见”首页,便能采用一种fake的手段,只须要这些资源:
① 业务HTML骨架 => 最简单的index.hrml载入
② 内嵌CSS
这个时候,页面一旦下载结束即可完成渲染,在其它资源加载结束后再将页面从新渲染便可,不少时候前端优化要作的就是靠近这种理想载入速度,解决那些制约的因素,好比:
因为现代浏览器的与解析机制,在拿到一个页面的时候立刻会分析其静态资源,而后开线程作下载,这个时候反而会影响首屏渲染,如图(模拟2G):
若是作fake页优化的话,便须要将样式也作异步载入,这样document载入结束便能渲染页面,2G状况都能3S内可见页面,大大避免白屏时间,然后面的单个背景图片即是须要解决的工程问题。
CSS Sprite旨在下降请求数,可是与去处冗余问题同样,半年后一个CSS Sprite资源反而很差维护,容易烂掉,grunt有一插件支持将图片自动合并为CSS Sprite,而他也会自动替换页面中的背景地址,只须要按规则操做便可。
PS:其它构建工具也会有,各位本身找下吧
CSS Sprite构建工具:https://www.npmjs.com/package/grunt-css-sprite
正确的使用该工具即可以使业务开发摆脱图片合并带来的痛苦,固然一些弊端须要去克服,好比在小屏手机使用小屏背景,大屏手机使用大屏背景的处理办法
又好比,前端模板是将html文件解析为function函数,这一步骤彻底能够在发布阶段,将html模板转换为function函数,免去了生产环境的大量正则替换,效率高还省电;
而后ajax接口数据的缓存也直接在数据请求底层作掉,让业务轻松实现接口数据缓存;
而一些html压缩、预加载技术、延迟加载技术等优化点便不一一展开......
当请求资源落地后即是浏览器的渲染工做了,每一次操做皆可能引发浏览器的重绘,在PC浏览器上,渲染对性能影响不大,但由于配置缘由,渲染对移动端性能的影响却很是大,错误的操做可能致使滚动迟钝、动画卡帧,大大下降用户体验。
减小重绘、减小回流下降渲染带来的耗损基本人尽皆知了,可是引发重绘的操做何其多,每次重绘的操做又何其微观:
① 页面滚动
② javascript交互
③ 动画
④ 内容变化
⑤ 属性计算(求元素的高宽)
......
与请求优化不一样的是,一些请求是能够避免的,可是重绘基本是不可避免的,而若是一个页面卡了,这么多可能引发重绘的操做,如何定位到渲染瓶颈在何处,如何减小这种大消耗的性能影响是真正应该关心的问题。
工程化其中要解决的一个问题是代码调试问题,之前端开发来讲Chrome以及Fiddler在这方面已经作的很是好了,这里就使用Chrome来查看一下页面的渲染。
timeline能够展现web应用加载过程当中的资源消耗状况,包括处理DOM事件,页面布局渲染以及绘制元素,经过该工具基本能够找到页面存在的渲染问题。
Timeline使用4种颜色表示不一样的事件:
蓝色:加载耗时 黄色:脚本执行耗时 紫色:渲染耗时 绿色:绘制耗时
以上图为例,由于刷新了页面,会加载几个完整的js文件,因此js执行耗时必然会多,但也在50ms左右就结束了。
Chrome还有一款工具为分析渲染而生:
1 Show paint rectangles 显示绘制矩形 2 Show composited layer borders 显示层的组合边界 3 Show FPS meter 显示FPS帧频 4 Enable continuous page repainting 开启持续绘制模式 并 检测页面绘制时间 5 Show potential scroll bottlenecks 显示潜在的滚动瓶颈。
show paint rectangles
开启矩形框,便会有绿色的框将页面中不一样的元素框起来,若是页面渲染便会整块加深,举个例子:
当点击+号时,三块区域产生了重绘,这里也能够看出,每次重绘都会影响一个块级(Layer),连带反应会影响周边元素,因此一次mask全局遮盖层的出现会致使页面级重绘,好比这里的loading与toast便有所不一样:
loading因为遮盖mask的出现而产生了全局重绘,而toast自己是绝对定位元素只影响了局部,这里有一个须要注意的是,由于loading转圈的动画是CSS3实现的,虽然不停的再动,事实上只渲染了一次,若是采用javascript的话,便会不停重绘。
而后当页面发生滚动时,下面的支付工具条一直呈绿色状态,意思是滚动时一直在重绘,这个重绘的频率很高,这也是fixed元素至关耗费性能的缘由:
结合Timeline的渲染图
若是这里取消掉fixed元素的话:
这里fixed元素支付工具栏滚动时候是绿的,可是一样是fixed的header却没有变绿,那是由于header多了一个css属性:
.cm-header { -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); }
这个属性会建立独立的Layer,有效的下降了fixed属性的性能损耗,若是header去掉此属性的话,就不同了:
show composited layer borders
显示组合层边界,是由于页面是由多个图层组成,勾上后页面便开始分块了:
使用该工具能够查看当前页面Layer构成,这里的+号以及header都是有本身独立的图层的,缘由是使用了:
transform: translate3d(-50%,-50%,0);
Layer存在的意义在于可让页面最优的方式绘制,这个是CSS3硬件加速的秘密,就如header同样,造成Layer的元素绘制会有所不一样。
Layer的建立会消耗额外的资源,因此不能不加节制的使用,以上面的“+”来讲,若是使用icon font效果也许更好。
由于渲染这个东西比较底层,须要对浏览器层面的了解更多,关于更多更全的渲染相关知识,推荐阅读我好友的博客:
今天咱们站在工程化的层面总结了前几回性能优化的一些方法,以期在后续的项目开发中能直接绕过这些性能的问题。
前端优化仅仅是前端工程化中的一环,结合以前的代码开发效率探讨(【组件化开发】前端进阶篇之如何编写可维护可升级的代码),后续咱们会在前端工具的制做使用、前端监控等环节作更多的工做,指望更大的提高前端开发的效率,推进前端工程化的进程。
本文关联的代码:
https://github.com/yexiaochai/mvc
转自:http://www.cnblogs.com/yexiaochai/p/4901341.html