前端优化,其实就几句话: javascript
传输层面:减小请求数,下降请求量
执行层面:减小重绘&回流
传输层面的历来都是优化的核心点,而这个层面的优化要对浏览器有一个基本的认识,好比:css
① 网页自上而下的解析渲染,边解析边渲染,页面内CSS文件会阻塞渲染,异步CSS文件会致使回流html
② 浏览器在document下载结束会检测静态资源,新开线程下载(有并发上限),在带宽限制的条件下,无序并发会致使主资源速度降低,从而影响首屏渲染前端
③ 浏览器缓存可用时会使用缓存资源,这个时候能够避免请求体的传输,对性能有极大提升java
衡量性能的重要指标为首屏载入速度(指页面能够看见,不必定可交互),影响首屏的最大因素为请求,因此请求是页面真正的杀手,通常来讲咱们会作这些优化:webpack
① 合并样式、脚本文件web
② 合并背景图片json
③ CSS3图标、Icon Font前端工程化
① 开启GZip浏览器
② 优化静态资源,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骨架、异步模块加载器(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等构建工具。
这里这里要记住一件事情,咱们的目的是完成前端工程化,不管什么模块加载器或者构建工具,都是为了帮助咱们完成目的,工具不重要,目的与思想才重要,因此在完成工程化前讨论哪一个加载器好,哪一种构建工具好是舍本逐末的。