起初想要去了解如何提高网页加载性能,发现Yahoo发布的一款基于FireFox的插件YSlow。
本文浅谈YSlow团队的23条“Web性能最佳实践和规则”,基本从两个角度出发,为什么要这么干,以及如何进行实践。
PS:
现在Yahoo工程师团队给出最佳实践是35条,而通常人们知道的是YSlow-23条规则(比如说本人)。
本文由YSlow-23条规则为基点出发,后续会进行35条最佳实践的相关拓展。
附上链接: Best Practices for Speeding Up Your Web Site
减少页面的 HTTP 请求次数是你首先要做的一步。这是 改良首次拜访用户等候时间 的最主要的方法。犹如 Tenni Theurer 的他的博客 Browser Cahe Usage – Exposed!中所说,HTTP 请求在无缓存情况下占去了 40%到 60%的响应时间。
改善响应时间的最简单途径就是 减少组件的数量 ,并由此减少HTTP请求的数量。
CDN, Content Delivery Network 内容分发网络, 其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
在很早的时候,是没有CDN的概念和需求的。那时候我们网站所需要的javascript等文件,就是放在我们的网站目录中,其实这也是一种内容交付的方式,而且往往还是比较高效的。但直到有一天,我们做了各种各样的网站,我们就会发现另外一个问题:就是针对同一个javascript文件,浏览器可能会缓存多个版本。
之所以会这样做,是因为浏览器是根据域(Domain)来缓存内容资源的,只要域不一样,那么它就需要重复下载这些资源,而且使用同样的方式将它们缓存起来。
但是,这会带来一些小的问题:重复地下载,缓存这些同样的脚本文件是需要占用带宽和本地缓存文件空间的。
于是,人们想出来一个解决方法:既然浏览器是根据域来区分这些内容资源的,那么是否可以将这些内容都放在统一的一个域里面呢?这样就算是我们有很多网站,我们都可以使用同样的地址引用这些内容资源,那么就不会产生重复下载和缓存的问题了。
一个最直接也是最简单的使用CDN的做法:如果你有很多站点,他们之间可以共享某些内容(例如javascript,css,image等),那么与其每个站点放一份,就不如将他们统一地存在在一个地方,这样就可以减少下载的次数和缓存的体积了。e.g. 在引用jquery.js的时候,你可以下载后,引用本地地址,也可以直接引用google CDN的内容。
<script type="text/javascript"src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
空的src和href都会导致多余的HTTP请求,虽然不影响加载时间,但是会对服务器产生不必要的流量和压力,严重的以至于影响整个网站的用户体验。
e.g.
在页面加载的过程中,一个有着空src属性的img元素被JavaScript动态地赋值。这样做的问题是,在脚本执行之前元素就被浏览器渲染了(尤其是当你把脚本放到文档最后的时候)。所以浏览器依然会发起一个HTTP 请求,虽然它是一个空值。 雅虎的团队指出,如果你将img的src留空,可能你的本意是暂时不要显示任何图片,但在不同的浏览器其实还是会有一些额外的请求发生。例如
e.g.同样的问题也发生在href 这个属性上。有些时候,开发人员想用超链接来触发JavaScript的一个交互。这时问题就来了,当用户触发了“单击”操作,如果 href 是空的那么浏览器就向服务器发送一个HTTP 请求。
避免空的src和herf值。
解决留空src属性的问题:
你可以将初始图片设置为一个很小的默认图片(这个图片设置永不过期),而不是留空。
解决留空href属性的问题:
1、给 <a>
标签以href属性,并不连接到实际的页面:
<a href="#"></a> <a href="#nogo"></a> <a href="##"></a> <a href="###"></a> <a href="void(0);"></a> <a href="void(0)"></a> <a href=";"></a> <a href=""></a>
2、禁止跳转,并添加 cursor:pointer样式
<style> a{cursor: pointer} </style> <a>点击一</a> <a onclick="doSomething()">点击二</a>
3、给a 标签创建一个带有描述信息的href 属性,并监控click事件调用preventDefault()函数。
<a href="#Something_De scriptive" id="my_id">Trigger</a> <script> $("#my_id").click(function(e){ e.preventDefault(); //取消单击事件的默认动作以阻止链接的跳转。 // 其他的代码 }) </script>
优点:
<a>
够响应键盘事件并获得焦点(从而屏幕阅读器能够读出背后的内容,增强可访问性) <a>
依然有手型与正常的link样式。 使内容具有缓存性。
格式:
Expires = "Expires" ":" HTTP-date
e.g.
Expires: Thu, 01 Dec 1994 16:00:00 GMT (必须是GMT格式)
通过HTTP的META设置expires和cache-control
<meta http-equiv="Cache-Control" content="max-age=7200" /> <meta http-equiv="Expires" content="Mon, 20 Jul 2009 23:00:00 GMT" />
参考资料: HTTP头的Expires与Cache-control
Gzip是一种流行的文件压缩算法,现在的应用十分广泛,尤其是在Linux平台。
当应用Gzip压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。这取决于文件中的内容。 除了节省流量,改善用户的浏览体验外,另一个潜在的好处是Gzip与搜索引擎的抓取工具有着更好的关系。Gzip开启以后会将输出到用户浏览器的数据进行压缩的处理,这样就会减小通过网络传输的数据量,提高浏览的速度。
参考资料: Apache2.4开启GZIP功能
提高渲染性能,避免浏览器重绘页面元素。
在HTML文件 <body>
中指定外部样式表和内联样式块可能对浏览器的渲染性能产生不利影响。
1)浏览器阻塞渲染网页直到所有外部的样式表都已被下载。
2)(用 <style>
标记指定的)内联样式块可能会导致reflows和页面跳动。
因此,把外部样式表和内联样式块放在页面的 <head>
中是很重要的。通过确保样式表首先被下载和解析,可以让浏览器逐步渲染页面。
将内联样式块和 <link>
元素从页面 <body>
移动到页面 <head>
中。
HTML 4.01规范(第12.3节)规定,始终把使用 <link>
标签的外部样式表放在 <head>
部分里。不要使用@import。还要确保您指定的样式有正确的顺序。
把 <style>
区块放在 <head>
部分里。
1、浏览器在加载JS时会阻塞浏览器的渲染操作,使页面加载时间更长,造成页面停滞。2、dom操作在页面加载完毕之前可能出错。
将脚本定义或引用放置到 <body>
底部。
<script defer="defer">
defer 属性规定是否对脚本执行进行延迟, 脚本将在页面完成解析时执行。
这里有一篇很有意思的文章,有兴趣的可以阅读一下。
JS 一定要放在 Body 的最底部么?聊聊浏览器的渲染机制<script src="Scripts/jquery-2.0.0.min.js"></script> <script> $(function () { $("body").css("background-color", (new Date()).getHours() % 2 ? "#B8D4FF" : "#F08A00"); }); </script>
1、提高了脚本文件和样式表的复用性。(无需再每个页面中都定义一次)
2、减少了页面体积,可以提高页面加载速度。(脚本文件和样式表可以被浏览器单独缓存)
3、提高了脚本和样式表的可维护性。(这个虽然与性能无关,但其实也是很重要的)
缺点:因为有单独的文件,所以可能会增加额外的请求。这违背了减少HTTP请求的原则。
<link rel="stylesheet" href="style.css" type="text/css" /> <script src="myjs.js" type="text/javascript" />
查找DNS是需要花费时间的,经验的总结是至少需要20毫秒左右的时间。在此期间,浏览器是无法下载其他任何内容资源的。所以浏览器会想办法对DNS的查找结果进行缓存。而除了浏览器的缓存之外,操作系统也会对DNS查询的结果做缓存。只不过,由于浏览器使用太过频繁,目前的主流浏览器都使用自己独有的缓存,而不使用操作系统的缓存。
不同浏览器在缓存DNS的问题上也不尽相同(主要体现在时间上面)。
1、缓存时间较长,有利于重复利用DNS缓存,提高速度。
2、缓存时间较短,有利于及时地检测到目标站点的IP地址更新,以进行正确的访问。
1、由于DNS查找需要时间,而且只缓存一定时间,所以应该尽可能地减少DNS查找的次数。
2、减少DNS查找次数,最理想的方法就是把所以的内容资源都放在同一个域(Domain)中,这样访问整个网站就只需要进行一次DNS查找,这样可以提高性能,节省响应时间。
3、HTTP/1.1中推荐客户端针对每一个域只有一定数量的并行度(它的建议是2),那么就会出现下载资源的排队现象,这样就会降低性能。
4、折衷的做法:在一个网站里面使用至少2个域,但不多于4个域来提供资源。
尽可能减少这两种文件的体积,以便加快下载速度。
e.g.
1、去除不必要的格式符、空白符、注释符。这个操作,可以理解为是一种格式化,虽然它操作的结果其实是去除掉原始文件的那些格式。
2、模糊(obfuscation)处理JavaScript脚本源代码。
工具:
1、 JSMin :可以对JavaScript进行最小化处理
2、YUI Compressor:可以对JavaScript进行压缩,也可以对CSS进行压缩(java工具)。在线版本 Online JavaScript/CSS/HTML Compressor
3、 JSLint :检查脚本语法合法性工具
4、 Absolute HTML Compressor :HTML最小化处理工具(主要是格式化)。
重定向的意思是,用户的原始请求(例如请求A)被重定向到其他的请求(例如请求B)。这是HTTP世界中本来就存在的技术和现象,它本身没有所谓的好和坏,它的存在也确实有其理由,为此HTTP协议中,规定了两个状态码来标识着中场景。他们分别是:
301 Moved Permanently
,这个状态码标识用户所请求的资源被移动到了另外的位置,客户端接收到此响应后,需要发起另外一个请求去下载所需的资源。 302 Found
,这个状态码标识用户所请求的资源被找到了,但不在原始位置,服务器会回复其他的一个位置,客户端收到此响应后,也需要发起另外一个请求去下载所需的资源。 目前我们一直只要区分301和302即可。他们本质上的区别到底是什么呢? 301表示永久重定向,302表示临时重定向 。
凡是访问地址中,没有带文件名后缀的(例如aspx,asp等等),服务器都会尝试解析为一个文件夹,自动加上一个路径斜线,然后再查找内部的默认页面。
重定向是无法完全避免的,适当的使用重定向能为网站提供更好的功能。(例如本地化,用户体验等方面)。 但是过多的进行重定向也肯定会给网站性能带来显著的印象。
在定义链接地址的href属性的时候,尽量使用最完整的、直接的地址。例如:
使用www.cnblogs.com
而不是 cnblogs.com
cn.bing.com
而不是 bing.com
www.google.com.hk
而不是 google.com
使用 www.mysite.com/products/
而不是 www.mysite.com/products
在一个页面中重复引用一个脚本可能存在的问题:浏览器会重复下载并执行脚本文件。
对脚本进行有效的管理,并在编写页面的时候,仔细的进行引用。
ETag,全程为:Entity Tag,意思是实体标签,它属于HTTP协议的一部分,也就是所有的Web服务器都应该支持这个特性。它的作用是用一个特殊的字符串来标识某个资源的“版本”,客户端(浏览器)请求的时候,比较ETag如果一致,则表示该资源并没有被修改过,客户端(浏览器)可以使用自己缓存的版本,避免重复下载。
它比last-modified date更具有弹性,例如某个文件在1秒内修改了10次,Etag可以综合Inode(文件的索引节点(inode)数),MTime(修改时间)和Size来精准的进行判断,避开UNIX记录MTime只能精确到秒的问题。 服务器集群使用,可取后两个参数。使用ETags减少Web应用带宽和负载。
另外,下面对比 Expires、Cache-Control、ETag 。
响应标 | 优势 和特点 | 劣势 和可能的问题 |
---|---|---|
Expires | - HTTP 1.0就有,简单易用。- 服务器通过这个Header告诉浏览器,某资源直到某个时间才会过期,所以在没有过期之前,浏览器就直接使用本地的缓存了。 | - 因为这是时间是由服务器发送的(UTC),但如果服务器时间和客户端事件存在不一致,可能会有些问题。 - 可能存在版本的问题,因为如果在到期之前修改过了,客户端是不会知道的。 - Cache-Control中的max-age可以实现类似的效果,但更加好,因为max-age是一个以秒为单位的时间数,而不是具体的时间,所以不存在上面提到的第一个问题。 |
Cache-Contro | - 服务器通过一个Header(Last-Modified)告诉浏览器,某资源最后修改的时间。 - 浏览器在请求的时候,包含一个Header(If-Modified-Since),然后服务器可以进行比较,如果在该时间后没有修改过,则返回304。 - 它比Expires多很多选项设置 | - Last-Modified 也是一个时间,但该时间只能精确到秒,如果在同一个秒中有多次修改(这个在现在的环境下应该确实是可能的),则可能会发生问题。 |
ETag | - 可以更加精确地判断资源是否被修改,因为它不是一个时间值,而是对时间经过处理的一个长整型数值(当然具体算法我们目前还不得而知)。- 浏览器发起新请求时需要包含 If-None-Match。 | - 如果部署在服务器场环境中,配置不当的话,可能每个服务器会对相同的资源生成不一样的ETag,这样就增加了重复下载的可能性。 |
AJAX=Asynchronous JavaScript And XML,AJAX不是新的编程语言,而是一种使用现有标准的新方法。AJAX是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
由于AJAX其实也是需要发起请求,然后服务器执行,并将结果(通常是JSON格式的)发送给浏览器进行最后的呈现或者处理,所以对于网站设计优化的角度而言,我们同样需要考虑对这些请求,是否可以尽可能的利用到缓存的功能来提高性能。
对于AJAX而言,有一些特殊性,并不是所有的AJAX请求都是可以缓存的。
1、POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(这里可以优化的是,服务器端对数据进行缓存,以便提高处理速度)
2、GET的请求,是可以(并且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一地址的AJAX请求,不会重复再服务器执行,而是返回304。
有的时候,我们可能希望GET请求不被缓存,有几种做法来达到这样的目的。
1、每次调用的时候,请求不同的地址(可以在原始地址后面添加一个随机的号码)。
2、如果你所使用的是jquery的话,则可以考虑禁用AJAX的缓存。
$.ajaxSetup({ cache: false });
之前提过:
1、POST请求,不能使用客户端缓存
2、GET请求,可以使用客户端缓存
这个意义上来讲,使用GET会比POST而言,因为减少了请求数和数据的重复传输,有更好的一个性能表现。
在使用XMLHttpRequest(目前的AJAX都是基于它实现的)的时候,浏览器中的POST实现为两步走的过程,首先发送头部信息,然后再发送数据。但如果是使用GET的话,就只有一个TCP的包发送出去(除非有大量的Cookie),这样无疑可以提高性能。
【备注】一个TCP包的尺寸大约为1452字节。 除此之外,显示的项目中,并不是总能使用GET的,例如长度方面可能会有限制:The maximum URL length in IE is 2K, so if you send more than 2k data you might not be able to use GET.
HTML DOM 定义了访问和操作 HTML 文档的标准方法。
DOM 将 HTML 文档表达为树结构。
减少页面的DOM元素数量,有助于减小页面体积,并且也降低了维护这份DOM树的成本。
1、避免不正确地使用服务器控件。
2、减少不必要的内容(并不是所有的内容都必须放在页面上面的)。
如果数据量大,可以考虑分页,或者按需加载
什么情况下会发生404错误?
404 意味着Not Found,意思是说未找到资源。既然如此,那么至少会有两种原因导致404错误:
404错误会有什么影响?
看不到的影响:有时候,404错误发生了,用户可能根本没有感觉到。例如
看得到的影响:
避免404错误发生的措施:
如果对某个域(Domain)保存了Cookie,那么针对这个域的所有请求,都会发送这些所有的Cookie(哪怕当前请求根本用不着,例如针对图片的请求),大量的、重复的发送Cookie毫无疑问会增加网络的流量,并因此而降低请求被执行的性能。
从技术上上拉说,这个文件的内容是由网站控制的,它可以决定要写什么内容在里面,他也可以决定是否要加密。唯一的一个限制,这个文件的体积不允许超过4KB。
比如图片 CSS 等,Yahoo! 的静态文件都在主域名以外,客户端请求静态文件的时候,减少了 Cookie的反复传输对主域名的影响。只有访问主域名的时候才需要保存cookie,而cookie会自动地发送给当前域的所有请求。
Filters这个功能是IE当年为了提供更加丰富的一些页面效果而设计出来的。不仅仅是别的浏览器可能不支持,IE从9.0版本开始也放弃了这方面的支持。
有时得到的图片尺寸和具体显示尺寸不一样,为了在网页中显示出希望的尺寸,通常会通过下面的方法把图片进行缩放:
<img width="100" height="100" src="pic.jpg" alt="my pic"/>
浏览器下载到原始图片之后,如果原始尺寸与目标尺寸不符,就会需要对图片进行缩放(拉伸或者缩小),以便实现刚才所提到的目的。
我们需要在网页中显示什么尺寸的图片,就请图片设计人员提供什么尺寸的图片,而不是在网页中进行缩放。
1、每个网站都应该有该文件,浏览器在访问任何页面的时候,总是会尝试去请求这个文件(如果本地没有的话)。2、该文件通常应该命名为favicon.ico ,如果希望使用别的名称或者格式(例如PNG),则需要在页面的头部(Head)中定义引用(下面两句中的第一句是必须的)
<link rel="shortcut icon" href="http://example.com/favicon.ico" type="image/vnd.microsoft.icon"> <link rel="icon" href="http://example.com/favicon.ico" type="image/vnd.microsoft.icon">
3、该文件可以直接放在网站根目录,但也可以放在其他的主机,或者你想要的任何位置。如果不在默认的根目录下面,也是需要通过上面所提到的引用方式定义。
请不吝指教。