gzip是GNUzip的缩写,最先用于UNIX系统的文件压缩。HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术,web服务器和客户端(浏览器)必须共同支持gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如Apache,Nginx,IIS一样支持gzip。 gizp流程:javascript
Nginx中开启gzip: css
HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,主要基于SPDY协议(是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提高网络速度,优化用户的网络使用体验)。 优化原理: 根据上文中说的资源合并问题,浏览器能够同时创建有限个TCP链接,而每一个链接都要通过慢启动
,三次握手
,链接创建
,HTTP1.1为了解决这个问题推出了keep-alive,即保持链接不被释放,可是真正的这些链接下载资源是一个线性的流程:一个资源的请求响应返回后,下一个请求才能发送。这被称为线头阻塞,为了完全解决此问题,HTTP2.0带来了多路复用: html
如今回过头来探讨一下上文说的资源合并问题,有了HTTP2.0以后,咱们是否还须要合并资源,目前看须要遵循下面的原则:前端
中止合并文件 在HTTP/1.1中,CSS,JavaScript被压缩到了一个文件,图片被合并到了一张雪碧图上。合并CSS、JavaScript和图片极大地减小了HTTP的请求数,在HTTP/1.1中能得到显著的性能提高。 可是,在HTTP/2.0中合并文件再也不是一个好的办法。虽然合并依然能够提升压缩率,但它带来了代价高昂的缓存失效。即便有一行代码改变了,整个文件就要从新打包压缩,浏览器也会强制从新加载新的文件。vue
尽可能不要在HTML里内联资源 非特殊的代码(rem适配代码,上报代码等)以外,尽可能不要使用内联资源,在极端状况下,这确实可以减小给定网页的HTTP请求数。可是,和文件合并同样,HTTP/2优化时你不该该内联文件。内联意味着浏览器不能缓存单个的资源。若是你将全部页面使用的CSS声明嵌入了每个HTML文件,这些文件每次都要从服务端获取。这致使用户在访问任何页面时都要传输额外的字节。java
合并域名 拆分域名是让浏览器创建更多TCP链接的一般手段,浏览器限制了单个服务器的链接数量,可是经过将网站上的资源切分到几个域上,你能够得到额外的TCP链接,可是每一个拆分的域名都会带来额外的DNS查询、握手,新链接的创建,根据HTTP2.0多路复用的原则:HTTP2采用多路复用是指,在同一个域名下,开启一个TCP的connection,每一个请求以stream的方式传输,域名的合并能够带来更多的多路复用,以下图在chrome的Network面板中查看HTTP2.0,注意protocol和ConnectID相同则表示启用复用: react
<img src="data:image/gif;base64,R0..."/>
复制代码
在页面使用的背景类图片icon类图片,很少且比较小的状况下,能够把图片转成base64编码嵌入到html页面或者CSS文件中,这样能够减小页面的HTTP请求数。须要注意的是,要保证图片较小,通常超过5kb的就不推荐base64嵌入显示了。为何是5kb?。 同时,采用Webpack的url-loader能够帮咱们在不影响代码可读性的状况下,解决base64字符串问题。android
IconFont技术起源于Web领域的Web Font技术,它是把一些简单的图标制做成字体,而后让图标变成和字体同样使用,Icon 的设计和使用在近几年的发展中,也经历了由当初的 img 方案 到现现在的 svg 方案,有如下优势:webpack
首屏的快速显示,能够大大提高用户对页面速度的感知,所以应尽可能针对首屏的快速显示作优化,基于联通3G网络平均338KB/s(2.71Mb/s),因此首屏资源不该超过1014KB,剥离首屏须要的资源,非首屏的资源单独合并,采用懒加载。这个原则适用上文的资源合并和加载中的场景。git
将不影响首屏的资源和当前屏幕资源不用的资源放到用户须要时才加载,能够大大提高重要资源的显示速度和下降整体流量,对于移动web端常见的多tab页面,Webpack的CodeSplitting帮助咱们更加便捷实现按需加载。
不用多说,在目前流量费用还算比较高昂的状况下,帮助用户节省更多的流量能够避免用户的投诉,为了保证页面内容最小化,加速页面渲染,尽量节省首屏网络流量,页面中的图片资源推荐使用懒加载实现,在页面滚动时动态载入图片。
CDN是将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提升用户访问的响应速度和成功率。解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。 对于web页面来讲,将项目的js,css等静态资源存放在CDN是一个重要的优化手段,加入全部资源统一打包放在同一个域名下,很难达到用户就近获取的优点(目前最佳实践是html页面采用一个域名,静态资源文件采用CDN域名),所谓静态资源便是能够被浏览器缓存的资源,而对于html页面,因为是js和css等连接的入口,一般不采用缓存。经常使用的阿里云CDN和腾讯云CDN都有开放接口,开发者能够按需选择。
此预加载主要分为两个部分,一种是采用原生浏览器支持的API来对页面的一些资源进行预先拉取或者加载,另外一种是经过本身写逻辑来加载一些重要的资源,当即下面内容的前提是要当即目前移动web常见的hybrid架构,webview外壳+H5页面:
DNS 做为互联网的基础协议,其解析的速度彷佛很容易被网站优化人员忽视。如今大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析须要耗费 20-120 毫秒,减小DNS解析时间和次数是个很好的优化方式。DNS Prefetching 是让具备此属性的域名不须要用户点击连接就在后台解析,而域名解析和内容载入是串行的网络操做,因此这个方式能 减小用户的等待时间,提高用户体验 。 html <link rel="dns-prefetch" href="//haitao.nos.netease.com">
二者都是以<link rel="preload"> 和 <link rel="prefetch">
做为引入方式。
一个基本的用法是提早加载资源,告诉浏览器预先请求当前页须要的资源,从而提升这些资源的请求优先级,加载可是不运行,占用浏览器对同一个域名的并发数:
<link rel="preload" href="a.js" as="script" onload="preloadLoad()">
复制代码
用法是浏览器会在空闲的时候,下载资源, 并缓存起来。当有页面使用的时候,直接从缓存中读取。其实就是把决定是否和什么时间加载这个资源的决定权交给浏览器。
<link rel="prefetch" href="a.js">
复制代码
遗憾的是对于这两个接口,移动端的浏览器支持性很很差,这也是没有广泛推广开来的缘由。
另外还有Prerender,subresource,Preconnect属性,因为目前能支持到这些属性的机型太少,这里就不在赘述了。
关于业务逻辑的预加载,在这里我能够举一个微信小程序的例子。小程序主要分为渲染层和逻辑层,逻辑层有iOS或者Android的JavaScriptcore来运行,渲染层由各自的webview组件负责渲染。咱们用户实际体验到的UI仍是跑在咱们的webview里面,这个和大多数H5页面的渲染用的是一个组件。可是为何咱们体验小程序会比H5页面要快不少?尤为是新开页面时?
借助小程序的思路,咱们的移动web一样适用这种预加载优化逻辑:
在多tab的单页应用中,咱们能够在用户打开首屏以后,预先加载其余tab的资源。例如用户进入时在推荐tab,这时就能够预先加载订单,个人 这两个tab的资源了,当用户点击订单时,页面的展示就会快一些。
预加载数据的时机最好是在空闲时,什么是空闲时呢?咱们分析一下打开一个H5页面的流程:
我利用闲时来作更多事情的前提是闲时够长,但这本书也不是一个很好的现象,尽可能的减小闲时,也是咱们须要作的一项优化,例如咱们来减小webview的加载时间,这就须要提早加载webview,此项优化大可能是由native端来完成:
上文说了浏览器缓存的基础知识,既然是基础,那就说明必须掌握,下面来讲一些进阶篇的利用缓存来优化页面:
HTML5 LocalStorage能够看作是增强版的cookie,数据存储大小提高,有更好的弹性以及架构,能够将数据写入到本机的ROM中,还能够在关闭浏览器后再次打开时恢复数据,以减小网络流量,平常使用localStorage来优化咱们的页面大概有如下几种场景:
离线包技术能够说是并不算很新的技术了,各个业务都有在使用,也都有本身的一套hybrid离线包系统,关键点在于离线包的打包,同时对文件加密/签名,更新离线包(增量) ,安全教研以及容错机制等等,在这里列举一些大厂的离线包方案来参考:
提到缓存,那就不得不提近几年比较火的Service Worker了:
在先后端分离以后,后端语言的模板功能被弱化,整个页面的渲染基本上都由前端 js 动态渲染,但这样对于一些应用来讲是有缺陷的。好比须要 SEO 的,须要打开页面不用等待就能看到页面的,另外前端页面展现过分依赖js和css逻辑执行,在极端状况或者网络较差,手机性能低下(尤为在低端Android机型较为明显)时,白屏时间较长,这时服务端渲染便应用而生,至于为何是Nodejs,做为一个前端,难道还要用Java么。。?
若是你说服务端渲染和早期web框架,例如SSH,JSP servlet,PHP等等同样的话,那我只能说呵呵,目前的服务端渲染和早期的框架是有本质区别的:
若是你的项目用的是React或者是vue,那么下面两个现场的开源框架是不错的选择。
固然,你也能够本身实现一套本身的服务端渲染框架,通常须要关注这些问题:
使用Nodejs的服务端渲染的一大优点就是代码同构,这使得一个项目能够分别部署成走线上正常前端渲染版本,和走服务端渲染版本,这样能够更好的作到容灾机制,当任何一种分之挂掉以后,能够直接走另外一个版本,提升稳定性。这也同构的魅力所在!由于在同构直出宕掉的时候,还有前端渲染页面能够提供正常的服务。
虽说服务端渲染这类优化确实能够提高必定的页面首屏时间,可是也是须要成本的,在前端开发接管了Node做为中间层时,须要额外的机器资源部署,而且一旦接触到后端,容灾机制,内存管理等性能指标都须要关注,这对于当前的业务系统架构可能须要有必定的调整,因此仍是要斟酌来使用。
终于回到咱们前端的老本家了,若是说前面的优化都是在框架,逻辑层面的优化,或者是参考后端,客户端的优化思路,那么真正涉及到UI渲染的优化才是咱们做为前端工程师的立身之本了。
抛开首屏加速,真正让用户体验web页面的另外一个很重要的部分就是用户行为交互了,这包括用户的点击相应,滚动流畅度,动画是否卡顿流畅度等等,这些关于用户交互性的优化在已往的PC端可能不是很被重视,由于PC浏览器的性能要远远大于手机端,可是到了移动web就不同了,用户都但愿移动web能有PC端同样的性能。
目前主流的Android硬件配置能够说是甩iPhone几条街了,那为何高配置却得不到好的体验呢?关键两类机型的操做系统上的优化程度,其中一个缘由就是iOS操做系统采用执行率较高的Object-c语言,大部分硬件接口能够直接调用和运行,而Android则采用Java语言,由于虚拟机的存在,虽然跨平台性提高了,可是经过虚拟机在和系统硬件交互,执行效率就低了不少,固然这只是其中一个缘由。那么,咱们移动web主要优化的群体就是Android机型了。
目前大多数设备的屏幕刷新频率为60次/秒,每一帧所消耗的时间约为16ms(1000 ms / 60 = 16.66ms),这16ms就是渲染帧的时长,所谓渲染帧是指浏览器一次完整绘制过程,帧之间的时间间隔是DOM视图更新的最小间隔,但实际上,浏览器还有一些整理工做要作,所以开发者所作的全部工做须要在10ms内完成。 若是不能完成,帧率将会降低,网页会在屏幕上抖动,也就是一般所说的卡顿,这会对用户体验产生严重的负面影响。因此若是一个页面中有动画效果或者用户正在滚动页面,那么浏览器渲染动画或页面的速率也要尽量地与设备屏幕的刷新频率保持一致,以保证良好的用户体验。在这一个间隔内,浏览器可能须要作如下事情:
- 脚本执行(JavaScript):脚本形成了须要重绘的改动,好比增删 DOM、请求动画等
- 样式计算(CSS Object Model):级联地生成每一个节点的生效样式。
- 布局(Layout):计算布局,执行渲染算法
- 重绘(Paint):各层分别进行绘制(好比 3D 动画)
- 合成(Composite):合成各层的渲染结果
复制代码
在上面浏览器须要作的这些事情中,会引起不一样程度的重绘和重排,而重绘和重排正式影响流畅的重要因素:
部分渲染树(或者整个渲染树)须要从新分析而且节点尺寸须要从新计算,这被称为重排。
因为节点的几何属性发生改变或者因为样式发生改变,例如改变元素背景色时,屏幕上的部份内容须要更新,这样的更新被称为重绘。
重排和重绘代价是高昂的,它们会破坏用户体验,而且让UI展现很是迟缓,可是每次重排,必然会致使重绘,而每次重绘并不必定会发生重排,咱们须要在如下几种场景来减小重排的发生: 当页面布局和几何属性改变时就须要回流。下述状况会发生浏览器回流:
提高动画流畅度的另外一个重要因素是让浏览器变得智能起来,好在浏览器给咱们提供了这个接口requestAnimationFrame,经过这个API,能够告诉浏览器某个JavaScript代码要执行动画,浏览器收到通知后,则会运行这些代码的时候进行优化,它会确保JS尽早在每一帧的开始执行,实现流畅的效果,而再也不须要开发人员烦心刷新频率的问题了:
function animationWidth() {
var div = document.getElementById('box');
div.style.width = parseInt(div.style.width) + 1 + 'px';
if(parseInt(div.style.width) < 200) {
requestAnimationFrame(animationWidth)
}
}
requestAnimationFrame(animationWidth);
复制代码
requestIdleCallback的出现伴随着React 16 的Fiber特性,他的使用场景是当用户在作负责交互时,不但愿由于一些不重要的任务(如统计上报)致使用户感受到卡顿的话,就应该考虑使用了,由于requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态,可是须要注意的是不要在requestIdleCallback操做任何DOM,这违背了这个接口的设计原则。
requestIdelCallback(myNonEssentialWork);
function myNonEssentialWork (deadline) {
// deadline.timeRemaining()能够获取到当前帧剩余时间
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
doWorkIfNeeded();
}
if (tasks.length > 0){
requestIdleCallback(myNonEssentialWork);
}
}
复制代码
在你使用dominnerHTML方法来插入大量dom节点时,不妨试试fragment,fragment文档片断是个轻量级的document对象,它的设计初衷就是为了完成这类任务——更新和移动节点。文档片断的一个便利的语法特性是当你附加一个片段到节点时,实际上被添加的是该片段的子节点,而不是片段自己。只触发了一次重排,并且只访问了一次实时的DOM。
长列表滚动在移动端是一种很是常见的交互模式,例如feeds流,图片流等等,这些列表的滚动流畅度优化对用户体验的提高是很是重要的,基于目前的优化思路,借助dom复用的方案,相似iOS的UITableView或者Android的recyclerview原理,在列表滚动时,只保证视窗区域内的dom节点存在,在有限的dom节点内实现滚动,而不在建立新的节点,在用户不断下拉翻页的过程当中,保证整个页面有限的dom元素来减小内存的消耗,原理以下图:
结论是若是要采用模拟滚动,能够解决onscroll不实时触发的问题,从而实现长列表的复用的优化,可是带来新的问题就是模拟滚动自己也是dom的重绘,增长额外的性能消耗,达到有优化效果并不理想,好在iOS的新版WKwebview解决了onscroll问题,让开发者有了更好的选择。
当持续触发事件时,必定时间段内没有再触发事件,事件处理函数才会执行一次,若是设定的时间到来以前,又一次触发了事件,就从新开始延时。以下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
复制代码
动画卡顿是在移动web开发时常常遇到的问题,解决这个问题通常会用到CSS3硬件加速。硬件加速这个名字感受上很高大上,其实它作的事情能够简单归纳为:经过GPU进行渲染,解放CPU,咱们能够利用GPU的图形层,将负责的动画操做放在这个层,如何开启?
webkit-transform: translateZ(0);
复制代码
强制把须要动画的dom的对象 ,放置在GPU的layout层来缓存从而达到任何移动,大小变化都在这个层。 经过开启GPU硬件加速虽然能够提高动画渲染性能或解决一些棘手问题,但使用仍需谨慎,使用前必定要进行严谨的测试,不然它反而会大量占用浏览网页用户的系统资源,尤为是在移动端,肆无忌惮的开启GPU硬件加速会致使大量消耗内存,千万不要* {webkit-transform: translateZ(0);}
。
本文在性能优化的基础上,将移动web的性能点逐步展开和深刻,内容比较多,指望各位开发者能真正实践并进行不断尝试,总之:
技术就是在于不断折腾,愿各位在踩坑的道路上一路顺风!
最后,向你们推荐一门慕课网的实战课程《移动Web APP开发之实战美团外卖》(当即学习),但愿小伙伴们能经过这门课程收获满满,祝你们学习进步。