内容主要来自网上InfoQ的一篇文章,该文章对HTML5性能分析得比较好。
1. 更简洁的标签 – 减少传输内容,缩减HTML文档的大小
HTML5是从HTML4继承过来的。HTML4里面有严格模式跟过渡模式,HTML5是支持这种过渡模式的,就是你可以不把一些标签闭合。但是,并不推荐所有的标签,比方说BODY标签的不闭合,这种不推荐。但是像P标签最常用的,还有列表标签LI,可不闭合。首先从视觉的角度来说,这样的方式更简洁一点。然后关键的是在文档传输过程中,内容会更少。
HTML5标签属性的声明支持三种方式:单括号、双括号和不加括号。为了减少文档大小,我是选择不加双引号的方式或单引号的方式。但是要注意,假设是类属性的声明,因属性可能包括多个类,多个类的时候则必须用括号括起来。谷歌自己有一个页面完全实践了上面的东西,文档的大小减少了20%,使HTML文档的传输减少了20%。如果把整个都实践起来,可以达到5%—20%之间的减少。这是第一步,缩减HTML文档的大小。
2.图片优化 – 使用CSS3
接下来是关于图片的优化,当图片多的时候,会严重拖垮整个页面的加载速度。关于图片的优化方式,总结起来主要有三点:使用精灵图、优化图片的大小,使用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秒之内,实际他之前的事情还没有做完,然后这个事情就会被延后。它就是为了动画看上去更流畅而优化的,因为每一桢的时候,它就告诉你说,你可以做一些事情。