前端性能优化规则总结—读《高性能网站建设指南》

本文对《高性能网站建设指南》这本书中提出的14种基本的前端性能优化方案进行了总结,这本书介绍的优化方案比较过时了,不能完全满足目前前端性能优化,如果您浏览完能弄清楚每种方案的实施过程。就没必要看这本书了。

规则1—减少HTTP请求

1.使用图片地图

    图片地图允许你在一个图片上关联多个URL,目标URL的选择取决于用户点击了图片上的哪个位置。

    比如导航栏菜单有五个选项,为了美观,我们将菜单对应的超链接关联到图片上,可以使用五个分开的图片分别关联五个分开的超链接,此时加载这个导航菜单就要通过五次HTTP请求下载五张图片,如果将五个单独的图标合成一张图片,使用图片地图就可以将HTTP请求减少到一次。具体HTML代码如下:

  1. <map name="map1" id="map1">
  2. <area shape="rect" coords="0,0,31,31" href="javascript:alert('Home')" title="Home">
  3. <area shape="rect" coords="36,0,66,31" href="javascript:alert('Gifts')" title="Gifts">
  4. <area shape="rect" coords="71,0,101,31" href="javascript:alert('Cart')" title="Cart">
  5. <area shape="rect" coords="106,0,136,31" href="javascript:alert('Settings')" title="Settings">
  6. <area shape="rect" coords="141,0,171,31" href="javascript:alert('Help')" title="Help">
  7. </map>

  在<img>标签中我们中加载了一张包含所有图标的图片,在<map>标签中定义了一个客户端图像映射,图像映射(image-map)指带有可点击区域的一幅图像。area 元素永远嵌套在 map 元素内部,定义了图像映射中的区域。<img>的 usemap 属性可引用 <map> 中的 id 或 name 属性(取决于浏览器),所以我们最好同时向 <map> 添加 id 和 name 属性。

这些标签的具体用户可以参考w3cschool<map>、<area>

缺点:

  • 图片地图上的区域坐标比较难确定

  • 图片地图中的图片要按次序并且连续

2.CSS Sprites

  和图片地图一样,CSS SPrites也可以合并图片,要使用CSS SPrites也需要将多个图片合并到一个单独的图片中,但是其对图片的次序、排列方向、是否连续没有硬性要求,因此推荐实际开发过程中采用此方法。任何支持背景图片的HTML元素,使用CSS的background-position属性,可以将HTML元素放置到背景图片中期望的位置(或者说,可以选取图片中期望的位置区域作为元素的背景)。关于background-position属性的用法可以参考background-position

  1. <div id="navbar" style="background-color: #F4F5EB; border: 2px ridge #333; width: 180px; height: 32px; padding: 4px 0 4px 0;">
  2. <a href="javascript:alert('Home')" title="Home"><span class="home"></span></a>
  3. <a href="javascript:alert('Gifts')" title="Gifts"><span class="gifts"></span></a>
  4. <a href="javascript:alert('Cart')" title="Cart"><span class="cart"></span></a>
  5. <a href="javascript:alert('Settings')" title="Settings"><span class="settings"></span></a>
  6. <a href="javascript:alert('Help')" title="Help"><span class="help"></span></a>
  7. </div>

  1. <style>
  2. #navbar span {
  3. width: 31px;
  4. height: 31px;
  5. display: inline;
  6. float: left;
  7. background-image: url(/images/spritebg.gif?t=1462159036);
  8. /*加载的图片仍为上面例子中的图片
  9. }
  10. .home {
  11. background-position: 0 0;
  12. margin-right: 4px;
  13. margin-left: 4px;
  14. }
  15. .gifts {
  16. background-position: -32px 0;
  17. margin-right: 4px;
  18. }
  19. /*比较多,后面的就省略了*/
  20. </style>
3.合并脚本和样式表

  JS具有阻塞特性,每次遇到<script>,页面都必须停下来等待脚本下载并执行,这会停止页面绘制,分散过多的js和css文件会导致HTTP请求增加。因此建议合理的合并脚本和样式表文件,从而减少请求次数。

规则2—使用内容发布网络(CDN)

     内容分发网络(Content delivery network或Content distribution network,缩写:CDN)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。对于前端开发来说,可以将CDN用于发布静态内容,如图片、脚本、样式表。

规则3-添加Expires头和Cache-Control头(使用缓存)

   浏览器(和代理)使用缓存来减少HTTP请求的数量,并减小HTTP响应的大小,使页面加载的更快。缓存的实现是Web服务器在相应中添加Expires头来告诉客户端它可以使用一个组件直到过期为止。

  1. /*从服务器发送到客户端*/
  2. Expires:Mon, 15 Apr 2024 20:00:00 GMT

    这个响应头告诉浏览器该响应的有效性持续到2014年4月15日为止。需要指出的是使用Expires头要求服务器和客户端的时钟严格同步并且要经常检测过期时间。在HTTP1.1中引入了Cache-Control头来克服Expires头的限制,它使用max-age指令指定组件被缓存多久,单位为秒,为了兼容低版本的HTTP协议,可以同时使用上述两个响应头,HTTP规范规定max-age指令将重写Expires头

  1. /*从服务器发送到客户端*/
  2. Expires:Mon, 15 Apr 2024 20:00:00 GMT
  3. Cache-Control: max-age=315360000

规则4—压缩组件(使用gzip编码压缩HTTP响应包)

   从HTTP1.1开始,Web客户端可以通过添加HTTP请求头Accept-Encoding来表示对压缩的支持

  1. /*从客户端发送到服务器*/
  2. Accept-Encoding: gzip, deflate
    如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来压缩响应。Web服务器通过响应中的Content-Encoding头来通知Web客户端。
  1. /*从客户端发送到服务器*/
  2. Content-Encoding: gzip
gzip是目前最流行和有效的压缩方法。

规则5—将样式表放在顶部(使用LINK标签将样式表放在文档的HEAD中)

    在HTML文件<body>中指定外部样式表和内联样式块可能对浏览器的渲染性能产生不利影响。浏览器阻塞渲染网页样式直到所有外部的样式表都已被下载。(用<style>标记指定的)内联样式块可能会导致重排和页面跳动。因此,把外部样式表和内联样式块放在页面的<head>中是很重要的。通过确保样式表首先被下载和解析,可以让浏览器逐步渲染页面。另外使用使用@import可能导致组件下载无序性,也就是存在其他组件已下载当样式文件仍未下载完成的情况,同样会阻塞页面渲染。因此建议使用LINK标签将样式表放在文档的HEAD中。

规则6—将脚本放在底部

     使用脚本时,对于所有位于脚本以下的内容,逐步呈现都被阻塞了,出现这一现象是因为脚本阻塞了并行下载。
HTTP1.1规范建议(只是建议)浏览器从每个主机名并行地下载两个组件,用户通过修改浏览器设置可以修改并行下载的数量,对于开发者来说,可以简单的使用CNAME(DNS别名)来将组件分别放到多个主机名中,但是主机名并不是越多越好,这取决于你的硬件和带宽性能。然而,在下载脚本时并行下载实际上是被禁用的—即使使用了不同的主机名,浏览器也不会启动其他的下载。一是因为脚本可能使用document.write来修改页面内容,因此浏览器会等待,以确保页面能够恰当布局。二是为了保证脚本能够按照正确的顺序执行(由于多个脚本之间可能才在依赖关系)。放置脚本的最好地方是页面的底部。这不会阻止页面内容的呈现,而且页面中的可视组件以尽早下载,虽然其请求时间较长,但对页面的影响很小。
需要说明的是这条规则并不是所有情况下都要遵循,例如使用脚本向页面插入了内容,为了让内容尽快呈现给用户,就不能将脚本移动到页面靠后的位置。

规则7—避免使用CSS表达式

  CSS表达式是动态设置CSS属性的一种强大且危险的方式,受到IE10(包括IE10)之前版本的支持,CSS表达式会自动绑定到浏览器事件中,
  1. p{
  2. /*IE浏览器会识别这一项,忽略min-width,其他浏览器则相反*/
  3. width: expression(document.body.clientWidth<600?"600px":"auto");
  4. min-width: 600px;
  5. border: 1px solid;
  6. }

   上面的示例保证段落的宽度至少是600像素,在IE浏览器中测试,当页面改变时,CSS表达式会重新计算,而在测试过程中(测试地址为Expression Counter)发现,改变窗口大小、滚动、鼠标拖拽,甚至鼠标移动,表达式都会被求值,如此频繁的求值势必会影响浏览器性能。避免该问题的方法是使用Javascript监听事件从而设置元素宽度。

规则8—使用外部Javascript和CSS

   一般情况下内联Javascript和CSS(通过<script>和<style>标签定义的)的页面加载速度会比使用外部Javascript和CSS文件快,这是因为使用外部文件增加了HTTP的请求量,但是外部文件所带来的收益是 JavaScript 和 CSS 文件有机会被浏览器缓存起来。 HTML文档,至少是那些包含动态内容的HTML文档通常不会被配置为可以进行缓存,当遇到这种情况时文档没有被缓存每次请求 HTML 文档都要下载内联的 JavaScript和 CSS另一方面,如果 JavaScript和 CSS 是外部文件,浏览器就能缓存它们, HTML文档的大小减小,而且不会增加 HTTP 请求的数量。

   这条规则也不是绝对的,关键因素是,与 HTML 文档请求数量相关的、外部 JavaScript 和 CSS 组件被缓存的频率。这个因素尽管难以量化,但可以通过下面几个基准衡量。

1.页面浏览量

毎个用户产生的页面査看越少,内联 JavaScript 和 CSS 的论据越强势。如果用户访问页面频率较低,即使缓存了外部文件也可能过期。
2.空缓存VS完整缓存

如果你的网站的本质上能够为用户带来髙完整缓存串,使用外部文件的收益就更大。 如果不大可能产生完整缓存,则内联是更好的选择。

3.组件重用
如果你的网站中的毎个页面都使用了相同的 JavaScript 和 CSS, 使用外部文件可以提髙这些组件的重用率。 在这种情况下使用外部文件更加具有优势,因为当用户在页面间导航时,JavaScript 和 CSS 组件已经位于浏览器的缓存中了。如果重用率较低,还是使用内联更有意义。
两全其美的技术
1.加载后下载

   对于作为多次页面査看中的第一次的主页,我们希望为主页内联 JavaScript 和 CSS, 但又能为所有后续页面査看提供外部文件。这可以通过在主页加载完成后动态下载外部组件来实现(通过 onload 亊件 ),这能够将外部文件放到浏览器的缓存中以便用户接下来访问其他页面。

  1. <script>
  2. function doOnload() {
  3. // Do the downloading awhile AFTER the onload.
  4. setTimeout("downloadComponents()", 1000);
  5. }
  6. window.onload = doOnload;
  7. // Download external components dynamically using JavaScript.
  8. function downloadComponents() {
  9. downloadCSS("http://stevesouders.com/hpws/testsm.css?t=1462242339");
  10. downloadJS("http://stevesouders.com/hpws/testsma.js?t=1462242339");
  11. downloadJS("http://stevesouders.com/hpws/testsmb.js?t=1462242339");
  12. downloadJS("http://stevesouders.com/hpws/testsmc.js?t=1462242339");
  13. }
  14. // Download a stylesheet dynamically.
  15. function downloadCSS(url) {
  16. var elem = document.createElement("link");
  17. elem.rel = "stylesheet";
  18. elem.type = "text/css";
  19. elem.href = url;
  20. document.body.appendChild(elem);
  21. }
  22. // Download a script dynamically.
  23. function downloadJS(url) {
  24. var elem = document.createElement("script");
  25. elem.src = url;
  26. document.body.appendChild(elem);
  27. }
  28. </script>
2.动态内联

   如果主页服务器知道一个组件是否在浏览器的缓存中,它可以在内联或使用外部文件之间做出最佳的选择。尽管服务器不能査看浏览器缓存中有些什么,但可以用 cookies 做指示器。如果 cookie不存在,就内联 JavaScript 和 CSS。如果 cookie 出现了 ,则有可能外部组件位于浏览器的缓存中 ,并使用了外部文件。
  由于每个用户开始的时候都没有 cookie, 因此必须有一种途径来引导这一过程。这可以通过使用前一个例子中的加载后下载技术来完成。当用户第一次访问页面时,服务器发现没有 cookie, 于是生成一个内联了组件的页面。然后服务器添加 JavaScript 来在页面加载后动态下载外部文件 并设置 cookie),下一次访问页面时,服务器看到了 cookie就会生成一个使用外部文件的页面。

规则9—减少DNS查找

      当客户端的 DNS 缓存为空(浏览器和操作系统都是)时, DNS査找的数量与 Web页面中唯一主机名的数量相等。这包括页面URL、图片、脚本文件、样式表、 Flash 对象等的主机名。 减少唯一主机名的数量就可以减少 DNS 査找的数址。But减少唯一主机名的数量会潜在地减少页面中并行下载的数量。 避免 DNS 査找降低了响应时间 ,但减少并行下载可能会增加响应时间。 正如规则6中“并行下载”中介绍的那样, 一定数量的并行下载是好的,因此建议是将这些组件分別放到至少 2 个,但不要超过 4 个主机名下。这是在减少 DNS 査找和允许高度并行下载之间作出的很好的权衡。另外,使用 Connection:Keep-Alive 也能带来好处,它可以通过重用现有连接,既能避免重复的DNS查找,也能避免 TCP/IP 开销来减少响应时间。

规则10—精简Javascript

    精简是从代码中移除不必要的字符以减小其大小,进而改善加载时间的实践。在代码被精简后,所有的注释以及不必要的空白字符(空格、换行和制表符)都将被移除。对于 JavaScript 而言,这可以改善响应时间效率,因为需要下载的文件大小减小了。
精简 CSS 能够带来的节省通常要小干精简 JavaScript, 因为通常 CSS 中的注释和空白比JavaScript少。最大的潜在节省来自于优化 CSS——合并相同的类、移除不使用的类等。CSS的依赖顺序的本质决定了这将是一个复杂的问题。这个领域还需要进一步研究和工具开发。最佳的解决方案还是移除注释和空白 ,并进行一些直观的优化,比如使用缩写(用 “#606” 代替 “#660066”和移除不必要的字符串 (用 “0”代替 “Opx”)。
在线 JS/CSS/HTML压缩工具
http://tool.oschina.net/jscompress


规则11—避免重定向

   重定向用于将用户从一个 URL 重新路由到另一个URL。重定向有很多种301 和 302 是最常用的两种。通常针对 HTM文档进行重定向 ,但也可能用在请求页面中的组件(图片、脚本等) 时。实现重定向可能有很多不同的原因, 包括网站重新设计、跟踪流世、记录广告点击和建立易于记忆的URL,但主要要记得的是重定向会使你的页面变慢。

规则12—删除重复的脚本

    在开发大型网站的业务中,各个团队负责不同的业务功能,很容易产生相同的脚本被添加多次的情况。
重复脚本损伤性能的方式有两种:不必要的 HTTP请求和执行JavaScript所浪费的时间,

  • 在 Internet Explorer 中,如果脚本没有被缓存,或在重新加载页面时, 会产生额外的HTTP 请求。
  • 在firefox和 Internet Explorer中,脚本会被多次求值。

规则13—配置ETag(或者干脆不使用)

   在熟悉这条规则之前,最后看一下http协议中关于缓存的知识。
实体标签(ETag)是 Web 服务器和浏览器用于确认缓存组件的有效性的一种机制,在进入 ETag的细节之前,我们先回顾一下组件是如何被缓存和确认有效性的。
   当请求一个组件时,服务器会根据其选项在响应中返回一个Expires头,浏览器会缓存该组件

  1. /*从服务器发送到客户端*/
  2. Expires:Mon, 15 Apr 2017 20:00:00 GMT

如果缓存的组件过期了(或者用户明确地重新加载了页面 浏览器在重用它之前必须首先检査它是否仍然有效)
服务器在检测缓存的组件是否和原始服务器上的组件匹配时有两种方式

  •  比较最新修改日期(Last-Modified)
  •  比较实体标签(Etag)
最新修改日期:(仅举例说明,示例测试可能已过期)
  •  比较最新修改日期(Last-Modified)
  •  比较实体标签(Etag)
最新修改日期:(仅举例说明,示例测试可能已过期)
原始服务器通过 Last-Modified 响应头来返回组件的最新修改日期。

  1. /*客户端请求*/
  2. GET /i/yahoo.gif HTTP 1.1
  3. Host: us.yimg.com
  1. /*服务器应答*/
  2. HTTP 1.1 200 OK
  3. Last-Modified:tue, 12 Dec 2006 03:03:59 GMT
  4. Content-Length: 1195

  下一次请求时浏览器会使用 If-Modified-Since 头将最新修改日期传回到原始服务器以进行比较。如果原始服务器上组件的最新修改日期与浏览器传回的值匹配,会返回一个304 响应。

  1. /*客户端请求*/
  2. GET /i/yahoo.gif HTTP 1.1
  3. Host: us.yimg.com
  4. If-Modified-Since:Tue,12 Dec 2006 03:03:59 GMT
  1. /*服务器应答*/
  2. HTTP 1.1 304 Not Modified

实体标签:
   ETag 提供了另外一种方式,用于检测浏览器缓存中的组件与原始服务器上的组件是否匹配。ETag 在 HTTP1.1中引入。 ETag是唯一标识了一个组件的一个特定版本的字符串。唯一的格式约束是该字符串必须用引号引起来。原始服务器使用 ETag 响应头来指定组件的 ETag。

  1. /*客户端请求*/
  2. GET /i/yahoo.gif HTTP 1.1
  3. Host: us.yimg.com
  1. /*服务器应答*/
  2. HTTP 1.1 200 OK
  3. Last-Modified:tue, 12 Dec 2006 03:03:59 GMT
  4. ETag: "10c24bc-4ab-457e1c1f"
  5. Content-Length: 1195
此后,如果浏览器必须验证一个组件,它会使用 If-None-Match 头将 ETag 传回原始服务器。如果 ETag 是匹配的,就会返回 304 状态码, 使响应减小 1195字节。
  1. /*客户端请求*/
  2. GET /i/yahoo.gif HTTP 1.1
  3. Host: us.yimg.com
  4. If-Modified-Since:Tue,12 Dec 2006 03:03:59 GMT
  5. If-None-Match: "10c24bc-4ab-457e1c1f"
  1. /*服务器应答*/
  2. HTTP 1.1 304 Not Modified
ETag 带来的问题

    ETag 的问题在于,通常使用组件的某些属性来构造它, 这些属性对于特定的、寄宿了网站的服务器来说是唯一的。 当浏览器从一台服务器上获取了原始组件,之后,又向 另外一台不同的服务器发起条件 GET 请求时, ETag 是不会匹配的 而对于使用服务器集群来处理请求的网站来说,这是很常见的一种情况。默认情况下,对于拥有多台服务器的网站,Apache 和 IIS 向 ETag 中嵌入的数据都会大大地降低有效性验证的成功率。
    Apache 1.3 和 2.x 的 ETag 格式是 inode-size-timestamp文件系统使用 inode 来存储诸如文件类型、所有者、 组和访问模式等信息。尽管在多台服务器上一个给定的文件可能位于相同的目录、具有相同的文件大小、权限、时间截等,从一台服务器到另一台服务器,其 inode 仍然是不同的。
  IIS5.0 和 6.0 在 ETag 上有着类似的问题。IIS上 ETag 的格式是 Filetimestamp:ChangeNumber. ChangeNumber 适用于路踪 IIS 配罝变化的计数器。 对于一个网站背后的所有IIS服务器来说, ChangeNumber 不大可能相同。
  IIS5.0 和 6.0 在 ETag 上有着类似的问题。IIS上 ETag 的格式是 Filetimestamp:ChangeNumber. ChangeNumber 适用于路踪 IIS 配罝变化的计数器。 对于一个网站背后的所有IIS服务器来说, ChangeNumber 不大可能相同。
    最后的结果是,对于完全相同的组件,从一台服务器到另一台, Apache 和 IIS产生的 ETag是不会匹配的。如果 ETag 不匹配,用 户就不会按照 ETage 的设计计划那样接收到更小更快的 304 响应,相反,它们会收到普通的 200 响应以及组件的所有数据。如果你只在一台服务器上寄宿网站,这不是什么问题,但如果你使用了服务器集群, 则组件的下载次数可能会比必须进行下载的次数多得多 ,这将导致性能的下降。ETag 还降低了代理缓存的效串.代理后面的用户缓存的 ETag 经常和代理缓存的 ETag 不匹配, 这导致不必要的请求被发送到原始服务器。用户和代理之间不会出现 304 响应,而是会产生两个又慢又大的200 响应—一个是从原始服务器到代理的, 另一个是从代理到用户的。并且 If-None-Match 比 If-Modified-Since 具有更髙的优先级。
  鉴于ETag带来的缓存性能问题,有两种办法绕过它:一种使对ETag进行配置,Apache 1.3.23 版和之后版本支持FileETag指令,使用这一指令,可以从ETag中移除 inode, 只留下大小和时间截作为组件的 ETagÿ 类似地,在 IIS 中可以为所有服务器设置相同的 ChangeNumber, 保留文件的时间戳作为 ETag 中仅有的另一块信息。另一种办法是直接移除ETag。

规则14—使Ajax可缓存

    使用Ajax会生成动态响应,只包含与这世界上的一个用户相关的信息。 缓存这些数据看上去没有任何意义。要记得的很重要的一点是,一个用户可能每天或毎周进行很多次请求(比如用户查看自己的邮箱中的邮件)。如果你可以为他缓存响应, 就会看到缓慢的和快速的用户体验之间的差距。
    使这些 Ajax 请求可缓存,除了改变 HTTP 头之外(添加Expires头)还需要进行更多的工作, 响应的个性化和动态本质必须被反映到缓存中。可供采用的最好的方式是使用査询字符串参数。例如

  1. /ws/mail /vl / formrpc?m=GetMessage&yid=steve_souders
    这个响应只对当前用户有效。可以将用户名放到査询字符串中来做到这一点。















posted @ 2016-05-04 18:14  FeMiner  阅读(366)  评论(0)    收藏  举报