书名:响应式Web设计性能优化
ISBN:978-7-115-39974-8
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [美]Tom Barker
译 余绍亮 丁 一 叶 磊
责任编辑 赵 轩
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
©2014 by O’Reilly Media, Inc.
Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2015. Authorized translation of the English edition, 2014
O’Reilly Media, Inc., the owner of all rights to publish and sell the same.
All rights reserved including the rights of reproduction in whole or in part in any form.
Tom Barker 资深Web技术专家,有20余年行业经验,专注于Web开发的各个方面。现为Comcast公司的Web开发高级经理,费城大学的兼职教授。痴迷于优雅的软件解决方案,软件持续改进,数据的提炼、分析以及可视化。 出版有 JavaScript性能优化:度量、监控与可视化(首部系统化阐述JavaScript性能优化的经典著作,Amazon全五星好评)
现如今,虽然响应式设计已经成为一个热门话题,但它仍然被当作一门前端技术。在大多数开发人员的印象中,响应式设计通常都与媒体查询有着紧密的关系。在本书中,我更愿意把响应式设计称为一种哲学,而不仅仅是一门技术:一个可以从传统单一的前端处理转变为从多方位考虑的理想解决方案,因为每个HTTP请求中都携带了许多信息到Web服务器,这样Web服务器就可以根据信息做相应的响应。在某些场景中,在后台实现响应式是一种更好的解决方案。
正因为周围的设计者和开发者常常有构建响应式站点方面的困惑,而一些公司也敏锐地意识到Web性能会带来非常大的挑战,我便萌生了写一本关于响应式设计的书的想法。如果只是关注客户端的响应式,而不去寻找更多的性能方案,逐渐地,响应式的优势以及我们的自身的效率都会陷入瓶颈。
当我在写这本书的时候,它就开始有了自己的生命。当我们注意到响应式网站的性能已经严重影响到网站的正常运行时,我们该如何去面对?如果我们为网页性能创建SLA,在开发阶段持续集成环境中,该怎么对性能进行测试?
本书将为你一一解答。
本书主要面向Web开发者,特别是那些只关注前端技术,而对后台技术涉足不深的Web开发者。这也是为什么我没有提那些老生常谈的前端性能,如CSS最佳实践之类的话题。如果你对这些感兴趣,你可以从其他很多地方获取相关的知识。同时这也是为什么我将JavaScript语言作为首选语言来写示例程序,尤其是使用NodeJS写后端示例代码。
也就是说,设计者、技术组长和任何层次的开发者都可以从本书丰富的示例与相关的知识中获益。
第1章中,我使用了排名前50的站点作为示例数据展示了在响应式设计中使用的设计模式和反模式。这些模式和反模式作为指导原则贯穿于整本书。我们也探讨了m. 站点的使用以及它们的优点与缺点。
第2章初步探讨了Web性能相关概念、Web运行时性能和监测性能的相关工具。如果你还不熟悉Web性能的方方面面,本章内容将是一个很好的参考。同时它也能很好地帮助你对不常提及的知识进行回顾,如客户端的内存消耗等。
第3章着重探讨了如何将响应式加入到项目的规划和培训阶段,尤其是对响应式网站性能进行说明的SLA。
第4章主要探讨了后端的响应式性能实现的相关概念。我们使用NodeJS编写了为客户端专有的设备体验的功能。同时,我们也使用第三方设备库来获得更多客户端功能相关的上下文,而不是自己简单地检测User Agent字符串,然后推导出设备功能。
第5章关注前端解决方案。通过picture元素和secret属性来加载特定设备专用的的图片,以及根据客户端特性,对图片和整个页面片段进行延迟加载的相关概念。最后,展示了如何使用客户端设备库API来决定表单因子。
第6章使用了PhantomJS来完成自动测试和SLA性能验证,并且把这些测试用例整合到Jenkins持续集成环境中。
第7章着重介绍了当前构建响应式网页的各种框架情况,也对它们的易用性、使用的模式和反模式、第三方依赖和对页面负载的影响等方面都做了一一比较。同时也介绍了我的开源服务端模板框架—Ripple, 它是基于第4章的示例代码开发的。
技术更新的速度总会超过我们写书的速度,即使我们一直不断的进行更新也是无法跟上技术更新的脚步。但是我不得不说,出版社做的是一件非常伟大的工作,它总是在第一时间将它们的书送到读者的手中。也就是说,在第1章中,我们展示美国排名前50网站的例子其实2013年12月统计的,在那以后,Alexa的统计一直不断更新,其余的网站一直不断的更新他们的网页。一些浏览器也已经迭代发布更新了他们加载和预加载资源的方式。而对于书中讨论的任何标准也是一样的。在你阅读这段话的时候,也许它们就已经被更新或者已经排上日程了。
在这里我想感谢我最美丽的妻子,Lynn,在这一年的创作时间里,是她的耐心陪伴我度过了每个深夜和周末。同样感谢我可爱的孩子们,我原本想在他们熟睡以后才写本书,但是有时候计划赶不上变化,同样感谢他们的耐心和理解。
我也要感谢Mary Treseler能给我这次机会,让我能够创作本书,他的博学和指导也让我受益匪浅。我也想对Colleen Lobner、Nick Lombardi、Melanie Yarbrough和 Dianne Russell表达我的感激之情,没有他们的帮助,也就没有本书的出版。我也要感谢Ilya Grigorik、Lara Swanson、Clarissa Peterson和 Jason Pamental,他们的精益求精,他们的热情敬业,一直帮助我修改书中的各种问题,他们对本书的完成也是至关重要的。
我曾与我的一个团队和产品经理参加了一个产品规划会议,讨论重新设计我们的视频区块,我们的团队主管提出了可以让这个区块具备响应式体验的方案。我们勾勒了这样一张页面,它会去加载默认的HTML5视频播放器,不过会根据用户所使用的设备来调整播放器的大小以及加载不同视频类型的资源与播放列表。这样我们的页面将会非常漂亮,能够适应更多的浏览设备,而且我们的视频就可以面向以前被拒之门外的各种设备的观众了。
这时我们的产品经理皱起鼻子说道:“我们的响应式首页出来之后,我就开始觉得我们的响应式设计思想有些问题。”
这让我甚感诧异。我们的响应式首页问题出在哪里?于是我开始着手做了些研究。
产品团队的印象是页面加载很缓慢。其实当开发人员在自己的笔记本电脑上演示时,一切看起来都很棒,然而当他们在真实设备上向高管们展示时,页面的加载时间实在太长太长了。
我查看了一下主页在智能手机和电脑渲染瀑布图。我所看到的就是我在许多其他站点中注意到的一些东西。
智能手机端在渲染的时候不仅加载了桌面端全部的资源文件,还加载了额外的CSS与sprite文件。图1-1表明相对于桌面端,在手机端的渲染增加了两个额外的HTTP请求,下载量也略微大一些(1.2MB vs 952KB)。
图1-1 主页在手机上渲染时的瀑布图
从图1-1中可以看到,共发送了134个HTTP请求,总的传输量是1.2MB。不过这是智能手机版的,通常情况下应该比桌面端的荷载要小。可事实并非如此,如图1-2所示。
图1-2 桌面版主页渲染时的瀑布图
可以看到桌面版本总荷载是952KB,来自132个HTTP请求。无疑,手机版除装载所有桌面版同样的内容外,还增加了两个文件。显而易见,这加剧了移动体验中的带宽问题。这完全与我们创建手机页面的初衷背道而驰了。
无独有偶,我在我的笔记本上打开浏览器,同时查看我iPhone上的HTTPWatch,然后访问了alexa.com排名前50的网站,做了一些竞争分析。我发现这些网站中,有30%的手机版的荷载要高于其对应的桌面版——科技公司、银行、零售商均是如此。
除了我自己的研究,许多著名的报告也有相似的结论。The Search Agency(一个全球数字营销代理机构)分析了零售百强以及财富百强公司的站点,做出了下面的报告。
提示
要想访问这些报告,需要将你的E-mail地址提供给The Search Agency,然后The Search Agency会将报告发送给你。
在这些结论中,图1-3展示出使用了(确切地说是滥用)响应式设计的站点比简单、普通的桌面站点平均多耗时1.91秒。更让人诧异的是,它们比手机专用的站点多耗时10.74秒。
图1-3 The Search Agency对响应式站点与手机专用以及桌面专用站点的平均加载时间比较
Guy Podjarny——Akamai公司的CTO——在其博客上也发表过一段文章,详细描述了他在运行类似测试实验时的发现。他比较了多种分辨率下的页面大小,发现其间的差别微乎其微。在http://bit.ly/1tBv6cT上可以找到他的文章。
我们都忘记了创建响应式体验的意义了吗?
我自己在对Alexa排名列表中的页面测试中也得到了一些有趣的数据。其中,我注意到了下面的情况。
图1-4 手机专用站点与响应式站点平均文件大小对比(单位:KB)
图1-5 手机专用的站点文件大小分布(单位:KB)
图1-6 响应式站点中文件大小的分布(单位:KB)
注意每个直方图中x轴的缩放比例。手机专用站点中有3个离群值接近于1MB;响应式站点中,1MB是第二大分组,且尾部一直延续到4MB。
图1-7 响应式站点在桌面和手机体验中HTTP请求的分组条状图
图1-8 桌面以及手机专用的“m.”站点体验中HTTP请求的分组条状图
在图1-7中的每个分组里,蓝条表示桌面体验中HTTP请求数目,黄条表示同一个页面在手机体验中的HTTP请求数。
同样,在图1-8中的每个分组中,蓝条表示桌面体验中HTTP请求数目,黄条表示手机体验中的HTTP请求数。
显然,我们的响应式设计的实现有问题。并且,提供一个专用体验的站点存在明显的优势,至少在HTTP请求数以及渲染一个页面需要的总传输荷载方面是这样的(不过也要注意,“m.”类型的站点也有其自身的一系列问题,我们后面会简短讨论)。应当注意到,贯穿本书,我的观点以及重复提到的主题自始至终都是响应式设计和专用体验并不是互斥的实现,而是同一理念的多个方面。
除了前面的指标,我还注意到我观察过的那些站点似乎是遵循了一些模式和反模式。
我在研究Alexa列表上的每个站点时,发现它们都存在一些共同的问题以及它们用到的一些反模式。接下来,我们来找出并研究一下这些反模式。
这些站点中,有一些会为手机和桌面渲染加载完全一样的资源。它们加载同样的CSS文件,通过媒体查询给不同的分辨率设备提供不同的体验。加载同样的图片,通过缩放来解决不同分辨率下的显示需要。
这种错误做法的后果会在HTTP拥堵时暴露无遗。在不同的显示设备中HTTP请求数目完全一样的那些站点极可能就是用了这种做法。当开始考虑更大分辨率的显示设备如苹果的Retina显示屏以及超高清电视时,这种解决方案就不能很好的扩展显示。
虽然为所有设备加载同一组资源忽略了设备间的本质差异,但在手机体验时加载除了通用资源以外的资源,就完全与我们所熟知的移动体验相违背了。这些额外的资源一般都是一些CSS以及sprite文件。
手机体验中比桌面包含更多HTTP请求以及更大荷载的站点常会表现出这种行为。正如前面所提到的,我自己的站点正是用了这种反模式。
最大的错误是,有些站点会为手机版本加载另外一组图片,如此一来图片文件的大小就是桌面版本图片的两倍了。这是常规桌面版图片之外的内容。
加载更大的图片然后再调整大小是为了让图片在小尺寸下显得更清晰。但这种做法的副作用就是会导致手机版站点的荷载比其桌面版大约要大30%。
所有这些问题都有几个共同的哲学思想。
并不是所有Alexa列表中的站点的做法都是错的——有些确实体验很好,为各种设备与分辨率做了优化。我们来看看它们用到的一些设计模式。
一些网站针对手机端加载了相比桌面端一半大小的图片(而不像之前所描述的反模式中加载两倍桌面端大小的图片),图1-9和图1-10展示了一个例子。
图1-9 针对手机端体验加载了专门为手机端准备的图片,120×70像素,2KB大小(截图自Chrome开发工具)
可以看到,在图1-9和图1-10中所展示的是同样的图片,它们的区别仅仅在于针对不同的客户端环境进行了不同程度的缩放。
图1-10 针对桌面端体验加载了专门为桌面端准备的图片,120×180像素,8.8KB大小(截图自Chrome开发工具)
同样,有些站点使用了设备独有的sprite图和CSS——并不是在桌面程序的基础上为其他设备增加其他资源。这样需要适当考虑网络带宽限制和传输成本。在alexa名单中的大多数站点都是使用专有“m.”网络来完成此功能的。我们同样也可以建立使用此模式的响应式网站,这部分的内容会在第4章中出现。
最好的浏览体验就是对于不同的设备提供完全不同的专有体验。有些站点提供了独立的“m.”网站,另外一些网站展示的是从服务端传输过来的基于设备独有布局和功能的页面。这种解决方案我们称之为RESS(响应式设计 + 服务端组件),但是对于我们常用来为预定义的分辨率断点来说,我们真的需要在一个“m.”站点中为每一部分功能传输加载适当的内容的同时,合并相同的业务逻辑。我们将在第4章讨论这种方案的更多细节。
为了更清晰地解释这个解决方案的架构,我们来看看图1-11所展示的这个架构的时序图。
图1-11 后端提供基于设备的专有体验的时序图
请注意,这些站点在提供专有体验的同时,保证了最小的荷载和最大限度的加速。这也就是为什么47%的顶尖站点仍然提供专有的网页内容。
有些站点不仅对图片进行延迟加载,而且对整个内容模块也进行延迟加载,包括上方和下方的折叠部分。通过这种方式,可以有效地避免为每个断点加载内容,智能地加载那些需要的内容,从而适应客户端性能,达到最佳的体验,但是是否延迟加载的决定权在客户端,而不是在服务端,我们将在第5章详细讨论相关细节。
图1-12用时序图展示了这种方法的细节。
图1-12 从客户端加载和设备相匹配的资源
本章的前面部分我已经描述了我们是怎么向产品经理展示我们的响应式主页的。经过一个迭代的开发,我们已经可以在我们的笔记本电脑上打开这个页面了,现在我们的桌面屏幕上可以满屏展示这个页面,并且可以自定义我们的浏览器窗口大小来适应不同的屏幕大小场景。虽然看着我们页面可以自适应屏幕大小是一件非常有趣的事情,但是在其他的设备上,它表现得一团糟。
我们在MacBook Pro上,也就是在我们的开发机上通过公司网络展示这个页面,当然看起来一点问题都没有。但是我们不能在我们预定的性能要求底限下工作(例如服务层协议或者SLA)。同时,我们甚至不能在非自己的设备上测试,最重要的是,我们不能违背SLA性能协议,至少我们不能比现有的主页性能低,毕竟在我们的性能监控平台上不能报警。在第3章中我们将详细讨论这个问题。
早在2008年左右,也就是在响应式设计出现之前,我们需要维护两个URL:mysite.com和m.mysite.com(我们的“m.”站点),在同样的Web App中,每一个站点都需要有不同的页面甚至不同的App来适配。它们可能由不同的团队负责,在当时,我们的想法很前卫,也很罕见,并且已经有了手机页面在使用了。
然后,在2011年,Boston Globe项目重新启动,响应式设计和渐进增强的想法已经成为了每次发布的博客和头脑风暴会议的主题了。我们阅读了关于如何创建自适应用户设备的响应式页面的文章。而且我们都被这些概念和想法深深地迷住了。从2000年以来,那些利用相对高度和宽度创建流式布局的的忠实拥
趸,一开始并没有感觉什么不同,但是当他们看到字体大小和图片同样可以被缩放的时候,他们也被这种新的想法深深地吸引了。
业界关于这方面的探讨也如火如荼地开展起来,有很多书出版了,演讲会也开展了起来,每个人都开始制作响应式网站。我们也开始讨论和使用媒体查询来封装不同尺寸屏幕的样式,而且尝试使用多种方法缩放图片。
当我们在真实项目中运用这些新的想法时,都应该从最小的屏幕尺寸开始,并且在打下坚实的基础后,逐步提高。事实上,我们的老板只想看到最终完整的版本(比如桌面版),这样,他们就可以向公司领导层邀功了。所以设计团队首先开工了,我们所有人都为实现这个版本而努力。但是我们可以巧妙地使用媒体查询来控制CSS来达到降低视觉体验的差异,看起来一切都可以解决的,对吧?
我们基于CSS和JavaScript来完成桌面版(最多只有几百KB的大小),然后加载到智能手机和平板电脑上,通过前端的功能我们可以确定客户端的详细信息。当一切都完成以后,我们可以向我们的老板展示,我们的老板也会向公司高层汇报,在一切就绪以后,项目就可以发布上线了。不可避免地,这样开发多少会造成代码的问题,以至于我们不得不花一些时间来进行重构。因为我们的CSS是基于桌面版的,是的,我们的所有link都最终连接到我们的桌面版代码中。然而,我们不能讳疾忌医,我们必须持续重构。因为我们产品的迭代速度非常快,我们需要资源来进行培训。
项目已经在运行中,但是现在的问题是,我们只关注前端样式是不是好看,页面是不是美观。媒体查询和图片自动缩放看起来很酷,但是,如果只关注这些表面的东西,我们就会从本质上错过了根据用户当前的设备来进行整体体验优化的机会,这不能被称作真正的响应式。我们不仅仅关注前端页面是怎么展现的,我们还把我们所有的判断逻辑都放置于前端。仅仅依赖于媒体查询来实现不同设备上的解决方案,或者通过JavaScript来测试前端,意味着我们向客户端传送了额外的文件。这种反模式的方法是我们已经察觉到的,如图1-13所示。
图1-13 反模式的时序图
不同设备之间的差异性,包括处理器能力、电池寿命和内存大小都会在我们将注意力集中在前端或者页面样式的时候被忽视。在实际项目中,这些都是我们需要在响应中应该注意的因素。这就是现在网络上主要的门户仍然维护着专有“m.”站点的原因。
到目前为止,我们都在讨论“m.”站点的好处,那你也许要问了,既然它这么好,我们没有理由不用它啊。这里需要申明的一点是:我并不认同“m.”。它当前的确在人们使用响应式设计的时候带来显著的性能提升。但是它同时也有一些缺点。
早在2000年的时候,当我创建我的第一个“m.”网站的时候,我必须让一个全新的工程师团队来开发和维护它。这主要是因为我们的产品团队并不想在建立主站的时候,影响移动设备的体验,这还因为移动站点是一项极其复杂和耗费精力的工作,我们不仅需要在主流的iOS和Android设备上支持它,还需要在其他操作系统的手机上支持它,这些手机都有不同大小的屏幕和特定的属性。包括对JavaScript甚至JavaScript的基本功能都支持的限制。
即使你不会使用一个单独的团队来维护,你仍然需要把你主站的相关工作,也就是“m.”网站,作为一个单独的项目来持续维护,事实上,某些功能在某些类型的手机上压根就不支持。
维护一个单独的网站也就意味着你需要维护一个单独的Web App和单独的一份代码。保持代码之间的同步是一个由来已久的问题,当前最主要的解决方法就是个人的自觉性和流程上保证,也就是说,最终它会变得混乱不堪和杂乱无章,当我们的代码没有办法进行同步,我们就失去了对代码的控制,我们最后很可能看到的是两个不同的网站,将来我们会花费更多的精力在更新上。
使用一个单独的“m.”网站也就意味着你需要创建和维护一个单独的URL,这和我们将一个资源作为一个统一的管理的整体思想相违背。对网站来说,一个“m.”就是第二个站点,此外,如果这样的话,我们的“m.”站点的未来将何去何从?它的底线在哪里呢?你会把它放到功能手机里面吗?或者智能手机?又或者平板电脑?那么平板电脑会遇到什么情况呢?它们都会进入到同一个“m.”站点吗?或者你能为不同的尺寸和特性分别维护一个单独的网站?你很快会发现,这种分割很快就会让你疲于应付、苦不堪言。
使用一个完全独立的URL也就意味着客户端进入网站的时候需要进行重定向。额外增加一个重定向也就增加了不必要的延迟体验,因为服务器需要向客户端返回302或者304状态码,然后客户端必须重新向一个新的地址发送新的请求,就像图1-14展现的那样。
图1-14 在手机站点上引入HTTP重定向的单独URL
到目前为止,我们主要都在讨论智能手机和桌面的体验问题,和平板一起,构成我们现在所知道的主要设备类型。但是工业的发展日新月异,在过去的几年里,我们看到过非常多的新设备和它们特有的功能,包括网络基础设施和客户端的一系列的特色。
举个例子,当苹果公司发布了Retina屏幕的时候,我们必须和设计团队一起创建独特的图片,让它们在Retina屏上也有不俗的视觉效果。随着Web开发在电视指南和App中显山露水,以及分辨率为4K和8K的超高清电视机的屏幕不断增大,这一趋势仍将持续。
另外一个例子就是谷歌眼镜现在变得越来越普及,所以我们也要考虑我们的站点在眼镜上的体检。现在Google提供了一套名叫Mirror的API,从而使得客户端库和Mirror API可以整合到一起(http://bit.ly/lrXkSpb)。
这些只是一些处于科技前沿的便携设备,越来越多的新科技将会涌现。如果我们继续把响应式设计看作一个前端的工具的话,那么我们将会发现页面臃肿的问题会越来越严重,也许我们会看到越来越多的公司将重新回归到“m.”站点中去。
工业的发展正在逐步影响响应式设计。在我统计过的站点中,几乎有一半的站点使用了自己专有的体验——和我们早在21世纪头几年使用的解决方案一样,而不是提供响应式站点。
响应式设计并不是一个有缺陷的方法论,只有在它被误用为一个附加的功能而非首要原则的时候,才会导致一种臃肿的、违反直觉的体验。同样,只有当我们将注意力集中到响应式的某一个方面,特别是前端特效时,我们才会失去对我们的响应式站点性能的敏锐观察力。然而,性能是响应式设计的一个重要的方面,作为交互的一个重要方面,需要慎重计划和设计。当我们在架构解决方案时,就要以此为基础。
在本章中,我们已经介绍了如何使用一些设计模式来影响响应式的性能。在接下来的章节中,我们会进一步介绍这些模式,并提出更多的模式。
如果我们不做这些功能并且在构建我们的响应式解决方案中考虑到性能问题,那么,当新的产品和设备带来越来越多的新特性后,越来越多的基于特定设备上的问题都需要解决,这会导致我们需要为每一种设备创建它独有的客户端交互方式。
如果你正在阅读本书,很可能你已经熟知性能的含义,或是至少曾经围绕你的Web应用做过一些性能相关的讨论。但在继续往下看之前,我们来确认下在术语方面我们的理解是一致的。
如若这是你首次听到Web性能优化一词,赶快去搞一本Steve Souders的High Performance Web Sites和Even Faster Web Sites(均由O’Reilly出版)读一读。这些都是Web性能的标准,也是所有Web开发者都应掌握的基础知识。
本章并不打算覆盖性能的每个细节。从前面提到的Steve Souders的著作开始,已经有大量的资料在讲这些东西。但是,本章的目标是对性能(Web性能和Web运行时性能)做个概述,包括一些性能度量的工具。这样一来,后面章节我们说到这些概念时,就不会再有歧义和混淆了。
当提及网站和Web应用性能的时候,我们说的要么是Web性能,要么是运行时性能。我们将Web性能定义为,一个终端用户从请求一段内容开始到这段内容显示在用户设备上这段时间的度量值。我们将运行时性能定义为,应用在运行时对用户输入响应能力的一个标示。
应当意识到,针对你的Web应用性能进行量化和标准的制定,是应用的一个关键方面。Web性能和运行时性能都有一些指标可以进行实证测度和量化。本章中,我们来看一看这些指标,以及可以用来量化这些指标的工具。
注意
性能指标是组织机构用来定义一次尝试是成功或是失败的可量化目标。通常称作关键性能指标,缩写为KPI。
本章我们将谈到的性能指标类型如下所示。
可以通过实验进行度量的一种目标(如某个东西的数量)。
不能通过实验度量的一种目标(如某个东西的质量)。
用于预测结果。
用于度量某个过程中消耗的资源。
想想每次浏览网页的过程。打开浏览器,键入URL,然后等待网页加载。从键入URL后按下回车键(或是从书签列表中点击某个书签,亦或是点击页面中的某个链接),到页面渲染,这之间消耗的时间就是所浏览页面的Web性能。若站点运行正常,这个时间甚至不应该被人感受到。
Web性能的定量指标数不胜数。
Web性能的定性指标总结起来比较简单:速度感。
在看这些指标之前,先来讨论下页面是如何到达浏览器并展现给用户的。当通过浏览器请求一个Web页面,浏览器会创建一个线程去处理这个请求,随后开始远程dns查找,远程dns服务器会将你输入的URL对应的IP地址返回给浏览器。
接着,浏览器通过与远程Web服务端的三次握手来建立一个TCP/IP连接。这个握手由浏览器与远程服务端之间的SYN、SYN-ACK以及ACK消息组成。
TCP连接建立之后,浏览器通过连接发送一个HTTP GET请求到Web服务端。Web服务端找到请求的资源,然后在HTTP响应中将其返回,状态200表示响应正常。如果服务端找不到请求的资源或是解析资源的过程中出错,抑或是资源被重定向,HTTP响应状态也会反映出这些情况。这个页面可以找到状态码的完整列表:http://bit.ly/stat-codes。下面是最常用的状态码。
图2-1是TCP事务的时序图。
图2-1 浏览器和Web服务器的协商过程
要时刻记得,加载一个HTML页面不只需要一次这个过程,浏览器还要为页面链接的每个资源发起一个HTTP请求——所有的图片、链接的CSS和JavaScript文件以及其他类型的外部资源(但是要注意,只要后续的HTTP请求连接的是相同的源,浏览器就可以重用相应的TCP连接)。
当浏览器收到页面的HTML后,就开始解析并渲染页面内容。
浏览器用其渲染引擎来解析和渲染内容。现代的浏览器架构由几个关联的模块组成。
这一层为浏览器绘制界面。有些元素,如地址栏、刷新按钮以及用户界面上(UI)的其他元素是浏览器自身的。
该层处理网络连接,承担的职责有建立TCP连接以及处理HTTP的往返过程。网络层处理内容下载,然后将内容传递给渲染引擎。
渲染引擎负责将内容绘制到显示器上。浏览器制造商会将他们的渲染引擎以及JavaScript引擎打上商标并对外授权。所以,相对流行的渲染引擎你可能已经听说过了。可以说,最流行的渲染引擎是WebKit,Chrome(Blink是它的译名)、Safari、Opera以及其他一些浏览器中都用到了WebKit。当渲染引擎遇到了JavaScript,会将其传递给JavaScript解释器。
JavaScript引擎会解析并执行JavaScript。如同渲染引擎,浏览器制造商给他们的JavaScript打上商标进行授权,很可能你已经听说过它们了。一个流行的JavaScript引擎是Google的V8,Chrome、Chromium中都用到了它,Node.js就是用它作为引擎的。
图2-2展示了这样的架构。
图2-2 分成多个模块组件的现代浏览器架构
设想这样一个用例,用户在浏览器地址栏里键入一个URL。UI层将这个请求传递到网络层,网络层随后建立连接,然后下载初始页面。当含有HTML块的数据包到达,就被传递给渲染引擎。渲染引擎将HTML组装成原始文本,然后对文本中的字符开始进行词法分析——或解析。这些字符会与一个规则集相比较——我们在HTML文档中指定的文档类型定义(DTD)——然后转换成基于规则集的符号。DTD规定了一系列标签,这些标签组成了我们将要使用的语言版本。这些标签就是由一些被分隔成有意义片段的字符组成。
这里有个网络层是如何处理并返回下列字符串的例子。
<!DOCTYPE html><html><head><meta charset="UTF-8"/>
这串字符会被分割成下面这样有意义的块。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
渲染引擎拿到这些符号后将它们转换成文档对象模型(DOM)元素(DOM是页面元素的内存表现形式,也是JavaScript用于访问页面元素的API)。DOM元素被布局成一棵渲染树,渲染引擎会迭代该树。首次迭代中,渲染引擎会布局好DOM元素的位置;下一次迭代就将这些元素绘制到屏幕上。
如果渲染层在解析和符号化过程中发现了script标签,就会暂停下来然后评估下接下来要进行的处理。如果script标签指向一个外部JavaScript文件,解析过程暂停,随后网络层介入,下载JavaScript文件,然后初始化JavaScript引擎解析,执行该文件。如果script标签包含的是内嵌的JavaScript,渲染引擎暂停,JavaScript引擎被初始化,内嵌的JavaScript会被解析与执行。执行完成后,之前暂停的渲染过程会恢复运行。
这是一个很重要的细微差别,影响的不仅仅是DOM元素何时对JavaScript可见(我们的代码可能会尝试访问页面上的一个元素,但该元素可能还没有被解析和符号化,更不用提渲染了)和性能。例如,我们是想要阻塞页面的解析直到下载并运行了那一段代码吗,或是如果我们先展示内容再去加载页面的代码,页面的功能能否正常?
图2-3描绘了这种工作流程。
图2-3 浏览器中内容加载和渲染的工作流程
了解网页内容是怎样传递给浏览器对于理解Web性能影响因素是至关重要的。也要注意到,由于浏览器的快速更新,浏览器厂商可能会时常对这个工作流进行调整和优化。
既然我们已经了解了内容传递和展现的架构,那来看一看在这个传递工作流上下文中的性能指标。
时刻记住,当浏览器获取HTML页面时会创建一个HTTP请求,还会创建更多的HTTP请求来获取页面中链接的每个资源。根据网络延迟情况,每个HTTP请求都会使总的页面加载时间增加20~200毫秒(如果考虑可以并行加载资源的浏览器,这个数字会有所不同)。如果只是少量的资源,这些额外的加载时间可以忽略不计,但如果是100个或更多的HTTP请求,将会显著加大总体Web性能的延迟。
减少页面需要的HTTP请求数才有意义。开发者有很多办法可以做到,如将不同的CSS或JavaScript文件合并成单个文件,将常用的图片合并成单个的称之为sprite的图片文件。
影响Web性能的因素之一是页面的总文件大小。总负载包括组成该页面的HTML、CSS以及JavaScript累计的文件大小。还包括所有的图片、cookie以及其他嵌在页面中的媒体。
HTTP请求数以及总的页面负载本身只是输入,但Web性能方面需要关注的真正KPI是页面加载时间。
页面加载时间是最明显的性能指标,也是最容易量化的。简而言之,它是浏览器下载并渲染所有页面内容的时间。以前,度量的是从页面请求到页面(窗口加载,window onload
)事件之间消耗的时间。最近,由于开发者越来越喜欢在页面完成加载之前就提供好的用户体验,这样度量结束的时间点就会移动,甚至完全改变。
特别是,在有一些用例中,window.onload
事件触发之后,可以动态加载内容——比如,如果内容是延迟加载的,就会出现这种情况——并且有一些用例,在window.onload
事件触发之前页面就是可用的,看起来也是完整的(如先加载内容,然后再加载广告)。这些用例会降低依靠window.onload
事件追踪特定页面加载时间的有效性。
有一些选择可以规避这个难题。WebPageTest的创建和维护者Pat Meenan,在WebPageTest中加入了一种度量方式,称为速度指标(Speed Index),其实质上是对页面内容渲染快慢计分。有一些开发团队创建了自己自定义的事件,来追踪他们觉得用户体验关键页面的各个部分是何时加载的。
无论你选择何种方式进行追踪,页面的加载(即,你的内容已经准备好接受用户交互)都是应当监控的关键性能指标。
追踪Web性能最常用和最有用的工具非瀑布图莫属了。瀑布图非常直观,可以展示构成Web页面的所有资源、加载这些资源所需的所有HTTP事务,以及每个HTTP请求消耗的时间。所有这些HTTP请求都展示成条状,一般y轴是资源的名称或URL。有的时候,资源的大小和资源的HTTP响应状态也会展示在y轴。x轴,或显式或隐式地展示出所消耗的时间。
瀑布图中的条形是按请求发生的顺序绘制的(见图2-4),条形区块的长度表示完成事务耗时的长短。在瀑布图的底部有时候也会看到总页面加载时间以及总HTTP请求数。瀑布图的妙处之一是,从条形的排列和重叠我们也可以发现某些资源的加载是不是阻塞了其他资源的加载。
图2-4 Firebug生成的瀑布图
现今,有许多不同的工具可以为我们创建瀑布图。有些浏览器提供了内置的工具,比如Firefox中的Firebug,或是Chrome的开发者工具。也有一些免费、托管的解决方案,比如webpagetest.com。
我们来看几个这样的工具。
最简单的生成瀑布图的方法是用浏览器自带的工具。这类工具有好多种,但在这一点上或多或少都有些类同,至少在生成瀑布图的方式上是相似的(有些浏览器内置工具远比其他一些工具有用,这一点在我们讨论Web运行时性能的时候将会看到)。
Firebug是首个广为采用的浏览器内置开发工具。以Firefox插件形式存在,由Joe Hewitt初创,Firebug确立了一项标准,不仅仅可以创建瀑布图来展示用于加载和渲染一个页面的网络活动,还让开发者可以通过控制台运行任意的JavaScript并展示错误,以及调试与单步调试浏览器中的代码。
如果你还不熟悉Firebug,可以访问http://mzl.la/1vDXigg进行安装。点击“Add to Firefox”按钮,然后按照说明操作即可成功安装这个附加组件。
注意
其他浏览器也有可用的Firebug,但一般都是简化版,没有提供像在Firefox中那样完整的功能。
图2-5 Firebug下载页
要在Firebug中浏览瀑布图,点击“网络”(Net)选项卡。
自Firebug出现以来,浏览器一直在发展,到现在,最新发布的浏览器都内置了一些工具,至少可以度量性能的某些方面。Chrome有开发者工具,Internet Explorer也有自己的开发者工具,Opera有Dragonfly。
要访问Chrome中的开发者工具,可以点击Chrome菜单图标,选择“工具-开发者工具”,如图2-6所示。
图2-6 访问Chrome中的开发者工具
在Internet Explorer中,选择“工具-开发人员工具”。
甚至在移动设备中,现在也有原生应用HTTPWatch,可以在其中运行一个浏览器,然后展示所有被加载资源的瀑布图。HTTPWatch可在http://bit.ly/1rY322j里下载。图2-7和图2-8可以让你对运行中的HTTPWatch有个粗略的认识。
图2-7 iOS 7上HTTPWatch中的资源加载过程
图2-8 iOS7上HTTPWatch中的Web性能信息
用浏览器内置的工具进行调试是非常不错的,但是如果你想找一些在持续集成环境中能用的自动化解决方案,就需要扩大选择范围了,包括平台解决方案或无外设的解决方案。
提示
第6章我们将详细讲解无外设的测试以及持续集成。
如前面所提到的,WebPageTest (www.webpagetest.org),是处于领先地位的解决方案之一,由Pat Meenan创建并持续维护。WebPageTest是一种托管解决方案,或者开源工具,你可以安装然后在你的网络上作为一个本地副本运行来测试你的防火墙。下载和托管的代码库:http://bit.ly/1wu4Zdd。
WebPageTest是一个Web应用,它的输入是一个URL(以及一堆配置参数),然后对这个URL运行性能测试。我们能配置的WebPageTest参数的数量相当多。
可以从一组全球范围的地点中选择一个来运行你的测试。每个地点都有一或多个浏览器供测试选择。还可以指定连接速度以及要运行的测试数量。
WebPageTest提供了有关站点整体性能的丰富信息,不仅包括瀑布图,还有展示给定页面的内容分布图表(负载的百分之多少由图片组成,多少由JavaScript组成等),模拟页面加载到终端用户的体验截屏,甚至还有CPU使用率,这个在本章后面会详讲。
最重要的是,WebPageTest是完全可编程的。它提供了一个API,可以获取所有这些信息。图2-9展示了WebPageTest中生成的瀑布图。
图2-9 WebPageTest生成的一个瀑布图
但在看Web性能指标时,要看的理想值是从你自己的用户那里得到的真实用户监控(Real User Monitoring,RUM)结果。要实现这个目标,需要一个完全可编程的解决方案,万维网联盟W3C已经制定了一个标准API,可以用来捕获和报告浏览器内的性能数据。这是通过性能DOM对象(在所有现代浏览器中,这是一个属于window对象的对象)来实现的。
2010年末,W3C创建了一个新的工作组,简称为Web性能工作组(Web Performance Working group)。据其网站描述,这个工作组的任务是提供方法来度量用户代理特性和API的各方面应用性能。从战略意义上这意味着该工作组开发了一个API,可以用JavaScript通过这个API获取关键性能指标。
Google的Arvind Jain以及来自微软的Jason Weber担任这个工作组的主席。可以通过http://bit.ly/1t87dJ0访问其首页。
Web性能工作组已经创建了一些新的对象和事件,不仅可以用来量化性能指标,还可以用来优化性能。下面是对这些对象和接口高层面的概述。
这个对象对外暴露了多个对象,比如Performancenavigation
、PerformanceTiming
、MemoryInfo
,以及可以记录亚毫米级高精度时间的能力。
这个接口让开发者具备检测指定页面是处于展现状态还是隐藏状态的能力,这样就可以针对如动画效果或是轮询操作中的网络资源优化内存利用率。
如果你在JavaScript控制台键入window.performance
,将会看到返回的Performance
类型的对象,这个对象对外暴露了几个对象和方法。截至本书写作时,标准对象集有PerformanceTiming
类型的window.performance.timing
、PerformanceNavigation
类型的window.performance.navigation
。Chrome还支持MemoryInfo
类型的window.performance.memory
。我们将在本章稍后的“Web运行时性能”部分讨论MemoryInfo
。
PerformanceTiming
对象对监控真实用户指标来说最为有用。图2-10所示为控制台中Performance
对象和PerformanceTiming
对象的一个截屏。
图2-10 控制台中展示的Performance
对象及展开的Performance.Timing
对象
要时刻记得,真实用户监控的目标是获取真实用户的实际性能指标,而不是实验室中人工测试或通过脚本测试获得的模拟的性能测试指标。RUM的好处在于能获取和分析基于实际用户的真实性能。
表2-1列出了PerformanceTiming
对象的属性
表2-1 PerformanceTiming
对象的属性
属性 |
描述 |
---|---|
|
在导航开始时采集,要么是浏览器开始卸载前一个页面的时刻(如果有的话),要么是浏览器开始获取页面内容的时刻。这将包含 |
|
在浏览器开始卸载以及结束卸载前一个页面时采集(如果同域下有前一个页面需要卸载的话) |
|
在浏览器开始以及完成所请求内容的DNS查找时采集 |
|
在浏览器开始以及完成任一HTTP重定向时采集 |
|
在浏览器开始以及完成当前页面到远程服务端的TCP连接建立时采集 |
|
在浏览器首次开始所请求资源的缓存时采集 |
|
在浏览器为所请求的资源发送HTTP请求时采集 |
|
在浏览器首次获取到以及完成接收服务端响应时采集 |
|
当文档开始以及结束加载的时候采集 |
|
当文档的 |
|
当页面的 |
|
在load事件被触发的时刻之前采集以及load事件触发之后立刻采集 |
图2-11展示了这些事件的发生顺序。
图2-11 性能计时事件
可以自己写一个JavaScript库嵌到页面中,然后从用户那里采集真实的RUM。本质上是通过JavaScript采集这些事件,再将它们发送到服务端,然后你就可以保存和分析这些指标了。我已经创建了这样一个库https://github.com/tomjbarker/perfLogger,欢迎使用。
正如我们已经讨论过的,Web性能跟踪的是内容传递到用户的耗时。现在来看看Web运行时性能,它跟踪的是用户与应用交互时应用的行为。
对于传统的编译类型应用,运行时性能是有关内存管理、垃圾回收以及线程等各个方面的。这是因为编译类的应用运行在内核之上,直接使用系统资源。
在客户端运行Web应用与运行编译类应用是大不相同的。这是因为Web应用运行在沙盒中,或者,说得更具体点,是运行在浏览器中。当它们运行的时候,用的是浏览器的资源。而浏览器是运行在它事先从内核中分配的内存资源中的。所以,当我们提到Web运行时性能,我们实际上说的是应用是怎样在客户端的浏览器运行,以及让浏览器在虚拟内存中其自身的内存里执行。图2-12是Web应用在常驻内存中的浏览器内存里运行的情况。
图2-12 一个Web应用运行在常驻内存中的浏览器预分配内存中
下面是我们需要考虑到的影响Web运行时性能的因素。
首先要看的是,我们有没有因为太多无用的对象以及创建更多对象时却仍保留这些无用对象而导致浏览器的内存分配被阻塞。随着时间推移,我们是否有什么机制限制JavaScript中的对象创建,或应用用的越多越久时,内存消耗是否也越多?是否存在内存泄露?
回收无用对象可能会导致浏览器在渲染或播放动画的时候暂停,容易在用户体验上出现锯齿现象。我们可以通过减少创建的对象数量以及尽可能重用已有对象来将垃圾回收次数降到最少。
我们更新DOM的时候是否引发了页面重绘?这一般是由于大范围的样式变化,需要渲染引擎重新计算页面元素的大小与位置。
当用户滚动页面时,我们有没有因为绘制一些区域而加重浏览器的负担?动画效果或是更新除了位置、缩放、旋转或透明度之外的任意元素属性,都将引起渲染引擎重绘对应元素并消耗时间。位置、缩放、旋转以及透明度是渲染引擎最后配置的元素属性,所以,更新这些属性只需极小的开销。
如果我们在宽度、高度、背景或者其他属性上使用动画,渲染引擎就需要重新考虑页面的布局并且重绘那个元素,这就会在渲染和动画过程中消耗更多的时间。更糟的是,如果我们引起了父元素的重绘,渲染引擎就需要重绘所有的子元素,严重影响运行时性能。
我们会在等待同步调用返回的时候阻塞用户的动作吗?当在操作复选框或其他方式接受输入后更新服务端的状态,再等待确认对应的更新操作已完成时,就会经常发生这样的事情。这会让页面感觉起来有些卡顿。
浏览器渲染页面和执行客户端代码需要多大负载?
我们要查看的Web运行时性能指标是每秒的帧数和CPU的占用率。
每秒帧数(FPS)是动画师、游戏开发者以及电影摄影师常用的一种度量单位。它是系统重绘屏幕的速率。按照Paul Bakaus的博客帖子“The Illusion of Motion”(http://bit.ly/1ou97Zn)中的说法,人类感觉动作平滑、逼真的理想帧率是60 FPS。
也有一个Web应用,叫每秒帧数 (http://frames-per-second.appspot.com),在浏览器中以不同的帧率演示动画效果。看这个演示,感受下自己的眼睛对同一个动画在不同帧率下的反应,很有意思。
FPS还是浏览器的一个重要性能指标,因为其反映出了动画运行以及窗口滚动的平滑程度。滚动时出现锯齿(卡顿)已经是Web性能问题的一个明显标志。
Google在创建浏览器内置工具追踪运行时性能方面在当前已是领头羊。其内置开发工具中已经包含了追踪FPS的能力。点击Rendering选项卡,然后选中“Show FPS meter”复选框,就可以看到(见图2-13)。
图2-13 在Chrome开发者工具中启用FPS meter
在浏览器的右上方会出现一个小的时间数列图,显示了当前FPS以及每秒帧数的趋势,如图2-14所示。使用这个工具,可以显式地追踪你的页面在实际使用过程中的执行情况。
图2-14 Chrome的FPS meter,位于Web页面的右上角
虽然FPS meter是很好的追踪每秒帧数的工具,但迄今为止,对在帧率方面体验下降进行调试的最有用的工具还是Timeline工具,这个也是Chrome开发者工具中的一员(见图2-15)。
图2-15 Frames模式下的Chrome Timeline工具
用Timeline工具,可以追踪以及分析浏览器运行的时候都干了些什么。它提供了3个操作模式:Frames、Events以及Memory。我们来看一下Frames模式。
这个模式下,Timeline工具展示了Web应用的渲染性能。图2-15是Frames模式的屏幕布局。
在Timeline工具中可以看到两个不同的窗格。顶部窗格展示的是活动模式(位于左手边),里面包含了一系列代表帧的竖条。底部窗格是Frames视图,这里展现的是形似瀑布的水平条状,标示了某个给定动作在帧里耗费的时长。在左边有对应动作的描述。在Frames视图的最右边是一个饼图,展示了在给定帧中最耗时动作的分类。所包含的动作如下所示。
图2-15展现出运行JavaScript耗去了将近一半的时间,1.02秒中占了525毫秒。
使用Timeline工具,在Frames模式下,通过在Frames视图下找到最长的条,就能轻易地确定对帧率影响最大的动作。
内存分析是监控我们应用所用到的内存消耗模式的一种方法。这对检测内存泄露与不会销毁的对象创建非常有用——JavaScript中,当我们用程序为DOM对象指定事件处理器,而后又忘记将事件处理器移除时尤为常见。更进一步,内存分析对优化内存占用也甚为有用。对象的创建、销毁与重用应当是智能的,要时刻注意,不要让剖析图中不断增加的一系列峰值呈上升趋势。图2-16描绘的是JavaScript堆。
图2-16 JavaScript堆中的对象
虽然浏览器内置的功能远比之前强大,但这仍是一个需要扩大和规范化的领域。迄今为止,Google已经做了很多,让开发者可以用上浏览器内置的内存管理工具。
在Chrome已有的内存管理工具中,首先我们要看的是MemoryInfo
对象,它存在于Performance
对象中。图2-17的截图中展示了一个控制台视图。
图2-17 MemoryInfo
对象
可以像这样访问MemoryInfo
对象。
>>performance.memory
MemoryInfo {jsHeapSizeLimit: 793000000, usedJSHeapSize:
37300000, totalJSHeapSize: 56800000}
表2-2展示出了与MemoryInfo
相关的堆属性。
表2-2 MemoryInfo
对象属性
对象属性 |
描述 |
---|---|
|
堆大小的上限 |
|
堆中当前所有对象使用的内存总量 |
|
堆的总大小,包括没有被对象使用的空闲空间 |
这些属性指出了可用和已用的JavaScript堆。堆是解释器保存在驻留内存中的JavaScript对象集合。在堆中,每个对象都是互有关联的节点,它们是通过诸如原型链或组合对象等属性连接起来的。浏览器中运行的JavaScript是通过对象引用来使用堆中对象的。当要销毁JavaScript中的一个对象,实际要做的就是销毁那个对象的引用。当解释器发现堆中对象不再有对象引用时,垃圾收集器将会从堆中移除这些对象。
用MemoryInfo
对象,我们可以获取用户群与内存消耗相关的RUM数据,也可以在实验室里追踪这些指标,好在代码产品化之前发现潜在的内存问题。
除了提供Frames模式来调试Web应用帧率之外,Chrome的Timeline工具还有Memory模式(如图2-18所示),能可视化地观察随时间变化的应用内存使用情况,并且会显示文档、DOM节点以及留在内存中的事件监听器的数量。
图2-18 Memory模式下的Chrome Timeline工具
顶部窗口展示的是内存剖析图,最底部的窗口展示了文档、节点以及监听器的数量。注意看,蓝色阴影区显示了内存使用率,可视化地展示了堆内存使用量。随着更多对象被创建,内存使用率也一直上升;当这些对象被销毁并被垃圾收集掉后,内存使用率就下降了。
可以在Mozilla开发网http://mzl.la/1r1RzOG找到一篇有关内存管理的很好的文章。
Firefox也开始开放内存使用数据,可以通过“about:memory”页面看到,Firefox的实现更多的还是通过静态信息页的方式而不是暴露一组API。正因为如此,它无法容易地插入程序中生成经验数据,about:memory页面更像是为Firefox用户(尽管是高级用户)设计的,而不是作为运行时性能管理的开发者工具集的一部分。
要在Firefox中访问“about:memory”页面,在浏览器的地址栏里键入about:memory。图2-19展示了这个页面的样子。
图2-19 Firefox的about:memory页面
如图2-19所示,可以看到以操作系统级别展示的浏览器分配的内存,以及浏览器打开的每个页面的堆分配情况。
本章探讨了Web性能以及Web运行时性能。我们关注了页面内容是怎样从Web服务器到浏览器的,以及在传输与内存渲染过程中存在的潜在瓶颈。还关注了在运行时表示Web应用执行情况的性能指标,这是性能的另一个关键点:不仅要看内存传输到终端用户有多快,还要看传输过去后应用的可用性。
我们使用了一些工具来量化和追踪两种类型的性能。
最重要的是,我们对本书剩余部分将要着重讲到的概念有了共同的认知。当我们谈及诸如降低页面负载以及HTTP请求数或者避免页面重绘这些概念时,可以翻回本章再看上下文。
第3章我们来看看如何让响应式成为业务方法论以及软件开发周期的一部分。