HTML5性能分析面面观
从性能角度来说,HTML5首先是缩减了HTML文档,使这件事情变得更简单。
第一,从用户可读性上说,原先一大堆东西,像初学者第一次看到这些东西是看不懂的,而HTML5的声明方式对用户来说显然更友好一些。 第二,文档编码的声明,用HTML5方式的话,就很简单。
很多人问HTML5是什么?我们说可以先用HTML5的方式就是把DOCTYPE先改了,因为目前很多页面都还是用传统的方式。HTML5的方式, 本身是兼容IE浏览器的,从IE6到IE10都可以,包括高级浏览器都支持。所以说拥抱HTML5最简单的方式就是把DOCTYPE给改了。
1.更简洁的标签
接下来可能并不是一件很常见的事情,但是却是我比较推崇的,使用更简洁的标签方式。HTML5从这个名字大家可以听出,它是从HTML4继承过来 的。HTML4里面有严格模式跟过渡模式,HTML5是支持这种过渡模式的,就是你可以不把一些标签闭合。但是,我并不推荐所有的标签,比方说BODY标 签的不闭合,这种我们不推荐。但是像P标签最常用的,还有列表标签LI。为什么这样说?首先从视觉的角度来说,这样的方式更简洁一点。然后关键的是在文档 传输过程中,内容会更少。
HTML5标签属性的声明支持三种方式:单括号、双括号和不加括号。为了减少文档大小,我是选择不加双引号的方式或单引号的方式。但是要注意,假设 是类属性的声明,因属性可能包括多个类,多个类的时候则必须用括号括起来。在这方面,给大家看一下谷歌的一个实践。谷歌自己有一个页面完全实践了上面的东 西,文档的大小减少了20%,使HTML文档的传输减少了20%。如果把整个都实践起来,可以达到5%—20%之间的减少。这是第一步,缩减HTML文档 的大小。
2.图片优化
接下来是关于图片的优化,图片永远是又爱又恨的元素。因为当图片多的时候,会严重拖垮整个页面的加载速度。关于图片的优化方式,《高性能网站》书中已有很多介绍,总结起来主要有三点:使用精灵图、优化图片的大小,使用DATA URI,具体这里就不细说了。
图片优化的另一个思路是:no-image。抛弃图片,拥抱CSS3。原先需要设置一张圆角效果的图片,现在使用CSS3中的 border-radius;原先需要设置阴影效果的图片,现在使用CSS3中的box-shadow;原先需要设置渐变的背景图片,现在使用CSS3中 的gradient。
3.预取
接下来讲Prefetching,预取,是优化的另一个思路。我们现在优化的思路无非就是少。很多都是从少的角度,比方说前面把文档大小减少,把图 片的大小减少。很多张的图片变成一张精灵图,都是为了把发送请求的数量减少。预取的话,是另一种思路,提早加载好资源,用户去点的时候,实际上已经加载 好,那肯定是更快了。
预取,一共有两部分:一部分是资源的预取,还有一部分是DNS的预解析。
资源预加载有几个点需要注意:
- 预加载只是在浏览器空闲的时候才会去拉,但不保证一定会去拉,这是很重要的一点。因为本身浏览器有一个全局的监听器,这是内部的一个接口,当浏览气空闲的时候,它会去执行浏览器空闲的时候应该做事情,但是这个空闲的回调不一定被触发,所以说并不保证一定会执行预加载。
- Chrome不支持HTTPS资源的预加载,像Alipay是HTTPS的页面,Chrome不会去预拉取。
- 一个预拉取的页面虽存在后不可见,实际上它是在正常解析。假如说我预拉取登陆页面,登陆页面有很多资源,比方说有图片,有CSS文 件,JS文件。它是从上往下正常的会被解析,解析的过程中,这个页面没有显现,但是它实际上是存在的。在HTML5里面,可通过 document.visibilityState得到当前页面状态,通常页面有两种状态,可见与不可见,但是现在有一个新的状态,叫做预渲染的状态。可 以直接通过document.visibilityState 是否等于 prerender 来判断页面是否在预渲染状态。
4.DNS解析
接下来是关于DNS的解析。有时候我们登入页面,对用户可能点的地方相对而言是比较难探测到,当然有时候我们会做一些埋点来探知用户下一步行为大部 分是往里走。但有些情况下,我们不知道用户下一步具体会走到哪一个页面的时候,但是我们知道他要走到哪一个域。这个时候,我就可以预解析DNS。因为实际 上,整个页面的请求过程中间有一个很长的DNS的解析过程,如果说这个我们提前做了,就可以更进一步让用户看到这一页面。
以下是Q+壁纸的案例。Q+壁纸是Q+某一个系统系统,首先Q+整个的架构是基于WEB + 客户端。我们现在看到的就是一个WEB的页面,虽然它外面是一个客户端的壳,但是它的心是WEB的。整个过程在我们第一次在完成的时候,因为图片比较多, 所有的静态资源是分配到十几个静态服务器上。也就是说,如果我要去拉的时候,我就要解析10个DNS,这个时间是相当耗时的,最慢的时候可能会延迟几秒 钟,这是我们肉眼能感觉到的。如果进行DNS预解析,因为本身资源我不知道具体是哪一个,所有图片都是随机的,所以我们只能说在DNS预解析上下功夫,来 提升它的速度。这样的话,从原来可能需要2秒钟,我就变成1秒钟。
接下来讲Q+中的应用。我们会像QQ里面一样,QQ里面跟Q+都有很多文字链,就是窗口的左下角有一个文字APP信息的推送。这边是通过WEB时时 去拉取后端,后端拉取过来然后在前台显示。但是在某一个时期,其实所有的APP它一共推送的运营信息是固定的。如果说按某个具体APP去分析每个文字链对 应数组的话,这个时候是非常大数据。因为这里一个就大概有达到三四百个字节,从优化的角度说,我们把这些每次拉区过来的存在本地。再存上本地的 localStorage,我们是同一域,所有的APP之间的信息都是可以相互访问的。然后就是把所有拉过的ID,就不会再重新拉一遍。
在这里也有一个需要注意的点,localStorage目前很多厂商的实现是同步的。如果你大量地调用localStorage这个接口,实际上他 会阻塞你的渲染进程。这个时候,当用户往下拖动页面的时候,然后你这个时候又正好在做存储数据,这个数据又比较大,这个时候用户就会感觉你这个页面非常 卡。之前他们都有讨论这个问题,本身这个接口的设计IE是设计成异步的,他们设计是成同步。这个会导致在调这个借口的时候,假设你程序比较多,因为有一个 序列化的过程,序列到磁盘。这样的话,整个过程就会显得比较慢。再加上本身localStorage可以做不同的窗口之间共享这个数据,它会在这个数据上 加锁。如果大量地数据在调用这个本地接口,它就会显得比较卡。所以目前没有什么特别好的解决方案,但是这是需要记住的。即使说目前最大的五点多兆,如果你 用了五点多兆,会让用户很悲催。因为你如果一去调用这个借口,用户在拖用鼠标,就觉得非常卡。
5.离线存储
接下来讲离线存储在性能方面给用户带来的好处。首先是进离线存储的定义文件,在Q+中所有的系统模块,都是有定义离线支持。就是说所有的应用,如果 网断了,还是可以用。在文档中加入MANIFEST的文件,MANIFEST是一个定义文件,声明当前页面哪些是需要存储在本地的?哪些是不需要存储的? 哪些如果说请求失败,应该用哪些新的图片或者什么来代替?这样分三块:
第一,CACHE,哪些需要存储到本地。
第二,NETWORK,是不会存储在本地的,它每次都回去请求一遍但是这里需要指出的是,本地存储跟浏览器存储实际上是两回事情,他们存的是两块不 同的地方。即使NETWORK这边需要告诉APP说,我需要每次都拉一次,因为像Chrome,他这个存储缓存是非常可恶的,比较难清除的,必须通过手动 去清除,才能完全生效。所以说你即使设置了不要让它存储在本地,但是浏览器可能本身把它存储起来了,因为他存的是两块不同地方。 第三,FALLBACK。如果说一个图片假如说请求失败,它是404。那要用什么图片代替?我觉得这个比较好玩。
MAEIFEST怎么设置? MANIFEST这里需要注意的是三点:
- MANIFEST同源限制;
- MIME类型必须为text/cache-manifest,这是标准的,如果是其他格式,都不会生效;
- CHROME,如果要看这个东西有没有生效,可能通过CHROME这个伪协议的方式在浏览器输入,chrome://appcache-internals。
关于如何去更新应用的缓存。为什么要离线存储?离线存储在本地,当浏览器知道你有离线存储你,它会首先去离线存储的目录下,去找这个资源是否已经被 Cache。当它已被Cache的时候,他就直接从这边拿到这个资源,不会再去发送一个请求。因为浏览器的请求是这样的,当有离线存储的话,就连请求都不 会发,所以说会更快。 如果说有的时候我们需要更新,更新的时候怎么办?
- 用户可以手动去清除浏览器的Cache,这个时候自动把本地存储给清除了。
- 修改MANIFEST的任何内容,这是比较推荐的方式,也是我们线上用的方式。就是说我们可以修改里面的的具体项目,但是这里应该最好是 修改注释,因为我每次发布的时候,我们自动发布机制,发布的时候在上面注释修改一下就可以了。这样的话,每次发布的内容,都会实时同步到客户端的本地;
- 通过程序去执行,程序的就是window.applicationCache.update()。就是我要去操作离线存储,其实我有时候叫应用存储,因为它的语意就是应用存储。我们去手动的更新应用存储。
6.Web Worker
接下来Web Worker。 Web Worker是一个多线程的JS进程。应用场景其实我们在线上的话,是没有的,我就不讲了。但是可以讲下具体我看到过的应用场景。
首先介绍一下WEBWORK是什么东西?它是一个OS级别的线程。之前我们模仿多线程,实际上都是多开一个窗口。但是现在的话,浏览器本身就提供了,这个会让操作带来更多便利,是让我们整个文档比较重,并不是很建议的方式。
然后WebWorker访问能力是有限的,它并不能访问到很多全局对象。比如说documnet对象它是访问不了的。 WebWorker最适合的场景就是CPU密集型的计算操作。之前我们做游戏的时候,我们用BOX2D。应该很多人听到过,它涉及到大量的计算,就是整个 页面里面,下面所有的物体要去计算它们的碰撞关系,这个计算量是非常大的。但是如果放在当前的JS的进程里面去执行,这个计算量一大,一计算,整个页面就 非常卡。但是如果用WebWorker去做,它是异步的过程,实时的发送过去,在计算的过程中还能干其他事,这就是多线程。
7.设备API
讲一下设备API。设备API我觉得最重要在性能方面,也是目前实现最早的API。一个是CONNECTION,就是网络带宽。这个有什么作用?在 中国这个场景下,必须得记住,很多用户的网速依旧是很低的。我们希望让用户网速低的时候,能够自动降级到一个比较低的方案。如果用现有的技术,我们是做不 到的。但是使用设备API我们是可以的。因为我们知道,从设备上可以取到这些信息。它的宽带是多少,多少宽带的时候我们能干的事情。比方说宽带好的时候, 我就用高清图片。宽带比较低的时候,就用清晰度比较低的图片。
8.电池
下面一个是关于电池的。我觉得从性能角度来说,主要是电量方面。假如说用户电池电量比较低的时候,我觉得是应该尽量少做一些事情。本身手机现在电池的技术来没有突破,我觉得让APP看起来比较高性能,也是一个宣传亮点。
9.CANVAS
接下来是CANVAS。讲CANVAS的几个性能优化点,用了这些东西,性能会有10倍的提升。
第一,每个CANVAS就是一个画布,我们要去渲染一个图形的时候, 我们是可以把它分层的。就是像PS里面一样,是一层两层三层。很多用户在做游戏的时候,直接把所有东西仿放到一个层里,一更新所有的东西都要更新。但如果 你把它分层,你让背景放在背景层,角色放到角色层。这样的话,我要更新角色的时候,只会更新角色,背景层不需要变。让CPU干的事情更少了,性能自然而然 就提升了。
第二,context.drawImage。不要去缩放图片,我们一开始就犯了一个错误,我们的美工做的图片始终跟我们不一致,然后我们要去缩放图 片。因为本身设备它的图片大小是这样的,我们必须按比率缩放图片。缩放图片以后发现在低端设备下,比方说iPad或者iPhone就会非常卡,我们就想为 什么?就进行代码上的分析,当用这个方法时候,花费的时候特别多。
第三,requestAnimationFrame。这是专门为渲染优化的一个方法。它本身的原理是这样的,当浏览器每过一桢的时候,会触发这个方 法,当我在触发的时候,Canvas得到这个浏览器已经准备好做下桢的事情。如果用传统的方法,是不会去考虑你更多的东西,它只会知道我过了多少时间,我 就要执行。假如说用户之前被阻塞了,每10秒钟执行这一方法,在10秒之内,实际他之前的事情还没有做完,然后这个事情就会被延后。它就是为了动画看上去 更流畅而优化的,因为每一桢的时候,它就告诉你说,你可以做一些事情。