规则1 -减少HTTP请求
用户发出请求后等待的时间遵循二八原则:20%用于服务器处理,80%用于前端加载页面中的所有组件(图像,
样式表,脚本,Flash等)。减少组件数量、减少呈现页面所需的HTTP请求数量是更快响应的关键。
以下是减少HTTP请求数量的一些技术,同时仍然支持丰富的页面设计。
1、组合文件是通过将所有脚本组合到单个脚本中以及将所有CSS组合到单个样式表中来减少HTTP请求数的方式。当脚本和
样式表在不同的页面有不同的样式或脚本方法时,组合文件更具挑战性,但使用此种方式能通过减少http请求的次数来改
善响应时间。例如:同一个工程下,可能存在多个模块,各个模块之间有共通的地方,也有独立的地方。因此有如下需要
平衡的点:组合文件时,(文件变大引起加载时间的延长)与(不进行组合文件时需要通过一个http请求去加载不进行组
合的小文件导致的时间的延长)两者之间的平衡,并不是说要盲目的进行文件合并。
2、CSS Sprites是减少图像请求数量的首选方法。将您的背景图像合并为一个图像,并使用CSS background-image和
background-position属性来显示所需的图像段。
CSSSprites其实就是把网页中一些背景图片整合到一张图片文件中,再利用CSS的“background-image”,“background-repeat”,
“background-position”的组合进行背景定位,background-position可以用数字精确的定位出背景图片的位置。
在网页访问中,客户端每需要访问一张图片都会向服务器发送请求,所以,访问的图片数量越多,请求次数也就越多,造成延迟的可
能性也就越大。
所以,CSS Sprites技术加速的关键,并不是降低质量,而是减少个数,当然随之而来的是增加内存消耗。但在网站性能的提升前,也
就不足为道了。
3、图像映像将多个图像组合成单个图像。总体大小大致相同,但减少HTTP请求数量会加快页面速度。如果图像在页面中
是连续的,则图像映射只能工作,例如导航栏。定义图像映射的坐标可能很繁琐,容易出错。使用导航图像映射也不可访
问,因此不推荐使用。
4、内联图像使用data:URL方案将图像数据嵌入到实际页面中,这会增加HTML文档的大小。但是将内联图像组合到样式表
(缓存)中是减少HTTP请求并避免增加页面大小的一种方法。
关于内联图像的解读,内联图像是什么?
以往我们加载图片,会这么写:<imgsrc="../a.jpg"> 或者以css中的background-img:url("../a.png"),那么内联图片则是这么写的<imgsrc="data:image/png;base64,iVAGRw0KGDCFGNSUhEUgACBBQAVGADCAIATYJ7ljmRGGAAGElEVQQIW2P4DwcMDAxAfBvMAhEQMYgcAC
EHG8ELxtbPACCCTElFTEVBQmGA"/>
<imgsrc="data:image/png;base64,iVBOR....> data - 取得数据的协定名称;image/png - 数据类型名称;base64 - 数据的编码方法;iUANR.
... - 编码后的数据;: , ; - data URI scheme 指定的分隔符号。
使用内联图片的优点:减少http请求次数;做为背景平铺类的图片使用内联图片的话,减少http请求次数,并且不会影响加载速度;
使用内联图片的缺点:浏览器不会缓存内联图片资源;兼容性较差,只支持ie8以上浏览器;超过1000kb的图片,base64编码会使图片
大小增大,导致网页整体下载速度减慢;
规则2 -使用内容交付网络(CDN)
CDN 的工作原理
在描述CDN的实现原理,让我们先看传统的未加缓存服务的访问过程,以便了解CDN缓存访问方式与未加缓存访问方式的差别:
由上图可见,用户访问未使用CDN缓存网站的过程为:
1)、用户向浏览器提供要访问的域名;
2)、浏览器调用域名解析函数库对域名进行解析,以得到此域名对应的IP地址;
3)、浏览器使用所得到的IP地址,域名的服务主机发出数据访问请求;
4)、浏览器根据域名主机返回的数据显示网页的内容。
通过以上四个步骤,浏览器完成从用户处接收用户要访问的域名到从域名服务主机处获取数据的整个过程。CDN网络是在用户
和服务器之间增加Cache层,如何将用户的请求引导到Cache上获得源服务器的数据,主要是通过接管DNS实现。
下面让我们看看访问使用CDN缓存后的网站的过程:
通过上图,我们可以了解到,使用了CDN缓存后的网站的访问过程变为:
1)、用户向浏览器提供要访问的域名;
2)、浏览器调用域名解析库对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库一般得到的是该域名对应的
CNAME记录,为了得到实际IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负
载均衡DNS解析,如根据地理位置信息解析对应的IP地址,使得用户能就近访问。
3)、此次解析得到CDN缓存服务器的IP地址,浏览器在得到实际的IP地址以后,向缓存服务器发出访问请求;
4)、缓存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实
际IP地址提交访问请求;
5)、缓存服务器从实际IP地址得得到内容以后,一方面在本地进行保存,以备以后使用,二方面把获取的数据返回给客户端,完
成数据服务过程;
6)、客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。
通过以上的分析我们可以得到,为了实现既要对普通用户透明(即加入缓存以后用户客户端无需进行任何设置,直接使用被加速
网站原有的域名即可访问),又要在为指定的网站提供加速服务的同时降低对ICP的影响,只要修改整个访问过程中的域名解析部分,
以实现透明的加速服务,下面是CDN网络实现的具体操作过程。
1)、作为ICP,只需要把域名解释权交给CDN运营商,其他方面不需要进行任何的修改;操作时,ICP修改自己域名的解析记录,
一般用cname方式指向CDN网络Cache服务器的地址。
2)、作为CDN运营商,首先需要为ICP的域名提供公开的解析,为了实现sortlist,一般是把ICP的域名解释结果指向一个CNAME记
录;
3)、当需要进行sorlist时,CDN运营商可以利用DNS对CNAME指向的域名解析过程进行特殊处理,使DNS服务器在接收到客户端请
求时可以根据客户端的IP地址,返回相同域名的不同IP地址;
4)、由于从cname获得的IP地址,并且带有hostname信息,请求到达Cache之后,Cache必须知道源服务器的IP地址,所以在CDN运
营商内部维护一个内部DNS服务器,用于解释用户所访问的域名的真实IP地址;
5)、在维护内部DNS服务器时,还需要维护一台授权服务器,控制哪些域名可以进行缓存,而哪些又不进行缓存,以免发生开放
代理的情况。
CDN的技术手段
实现CDN的主要技术手段是高速缓存、镜像服务器。可工作于DNS解析或HTTP重定向两种方式,通过Cache服务器,或异地的镜
像站点完成内容的传送与同步更新。镜像站点(Mirror Site)服务器是我们经常可以看到的,它让内容直截了当地进行分布,适用
于静态和准动态的数据同步;高速缓存手段的成本较低,适用于静态内容。
CDN的不足
任何一个新事物,在给现有模式带来改进的同时,也必然存在一定的局限,CDN也是这样。实时性不太好是CDN的致命缺陷。随着
对CDN需求的逐渐升温,这一缺陷将得到改进,使来自于远程服务器的网络内容与复本服务器或缓存器中的内容保持同步。解决方法
是在网络内容发生变化时将新的网络内容从服务器端直接传送到缓存器,或者当对网络内容的访问增加时将数据源服务器的网络内容
尽可能实时地复制到缓存服务器。
规则3 -添加到期或缓存控制头
这个规则有两个方面:
对于静态组件:通过设置远远的Expires头来实现“永不过期”策略;
对于动态组件:使用适当的Cache-Control头来帮助浏览器进行条件请求;
网页设计变得越来越丰富,这意味着更多的脚本,样式表,图像和Flash中的页面。您的页面的第一次访问者可能需要进行多个HTTP
请求,但是通过使用Expires标题,您可以使这些组件可缓存。这样可以避免后续页面浏览中不必要的HTTP请求。过期标题最常用于
图像,但它们应用于所有组件,包括脚本,样式表和Flash组件。
浏览器(和代理)使用缓存来减少HTTP请求的数量和大小,从而使网页加载速度更快。Web服务器使用HTTP响应中的Expires标头来
告诉客户端组件可以被缓存多长时间。
请记住,如果使用远未来的Expires过期时间,则必须在组件更改时更改组件的文件名,这样再次使用时才会舍弃缓存,全新加载,
使本次的修改生效。在项目构建过程中将 CSS、JS等静态文件添加版本号可以达到以上效果,例如:menu.css??v=201704061200。
规则4 - Gzip组件
GZIP压缩的工作原理:
1. Web服务器接收到浏览器的HTTP请求后,检查浏览器是否支持HTTP压缩(Accept-Encoding信息);
2.如果浏览器支持HTTP压缩,Web服务器检查请求文件的后缀名;
3.如果请求文件是HTML、CSS等静态文件,Web服务器到压缩缓冲目录中检查是否已经存在请求文件的最新压缩文件;
4.如果请求文件的压缩文件不存在,Web服务器向浏览器返回未压缩的请求文件,并在压缩缓冲目录中存放请求文件的压缩文件;
5.如果请求文件的最新压缩文件已经存在,则直接返回请求文件的压缩文件;
6.如果请求文件是动态文件,Web服务器动态压缩内容并返回浏览器,压缩内容不存放到压缩缓存目录中;
使用GZIP同时也需要客户端浏览器的支持,目前大部分浏览器都已经支持GZIP了,如IE、Mozilla Firefox、Opera、Chrome等,通过查
看HTTP头,我们可以快速判断使用的客户端浏览器是否支持接受gzip压缩。
若发送的HTTP头中出现以下信息,则表明你的浏览器支持接受相应的gzip压缩:
Accept-Encoding: gzip——支持mod_gzip;
Accept-Encoding: deflate——支持mod_deflate;
Accept-Encoding: gzip,deflate——同时支持mod_gzip和mod_deflate;
关于vary的解释:
先来看看下面这幅图:
这个图显示了一个网页从请求到响应的过程。正常情况下,“Response”的结果是可读文本,但并不是所有的服务器端都返回这样的
正常的结果到用户端,有的返回一堆乱码,这显然是不正常的。
当浏览器发出一个请求时,会包含一些HTTP头信息,服务器会根据这些头信息决定返回什么样的东西(这是一个移动客户端吗?
它能否处理压缩内容?它是否需要特定的语言支持?)。
直接访问是好的,但现在网络使用了中间高速缓存(cache)和内容分发网络(CDN)。这就产生了一个问题,缓存如何使用头信
息决定返回什么?它能否复制服务器端的决策逻辑?
“Vary”解决了这个问题,“Vary”头描述什么信息“唯一地”标识一个请求——传入的请求只有完全匹配缓存的“Vary”信息,缓存才
被使用。
假如没有“Vary”头,那么如果由于某种原因,客户端有一个未压缩的版本在其缓存中的文件,它会不知道随后再次要求它的压缩
版本,而不是只从缓存中使用未压缩的文件。——这就很好的解释了“Vary”头信息的重要意义。
设想有两个客户,一个使用的旧浏览器不支持压缩,一个使用新的浏览器支持压缩,如果他们都请求同一个网页,那么取决于谁
先请求,压缩或非压缩版本便存储在CDN上。这样问题就出现了,旧浏览器请求常规网页但获得缓存的压缩版本,而新浏览器会获
得缓存的非压缩版本但尝试去“解压”它。无论哪种方式都是坏消息。解决方法是,源服务器回送“Vary: Accept-Encoding”。
现在的中间CDN会存储独立的缓存条目,一个是Accept-encoding: gzip,而如果你没有发送header,则存储另一个。
现在的新浏览器都支持压缩了,因此如果网站启用了GZip,可以无需再指定“Vary: Accept-Encoding”标头,不过指定
“Vary:Accept-Encoding”标头会有更高的保险,而它并不需要你额外的开销
规则5 -将样式表放在顶部、脚本放在底部
在web页面设计中,一般在HTML中不直接写样式,而是通过link标签引用一个CSS文件。在下载页面过程中,document最早开始下载,
然后浏览器解析document,下载相关的css、js、图片、字体、视频等资源。css文件放置在head中和放在body底部,对css的下载时间
不会有影响,但是对页面的
呈现有着非常大的影响,与用户的体验密切相关。将样式表放在文档顶部(Head中)能使页面加载得更快(改善页面加载时间)。
CSS文件放置在顶部的原理
CSS文件放在顶部一方面是因为放置顺序决定了下载的优先级,更关键的是浏览器的渲染机制。最理想的情况,我们希望浏览器逐渐
的渲染下载好的CSS,将页面逐渐的展现给用户。但是浏览器为了避免样式变化时重新渲染绘制页面元素,会阻塞内容逐步呈现,浏
览器等待所有样式加载完成之后才一次性渲染呈现页面。如此,CSS文件如果放置底部,浏览器阻止内容逐步呈现,浏览器在等待最
后一个css文件下载完成的过程中,就出现了“白屏”(新打开连接时为白屏,尔后先出现文字,图片,样式最后出现)。这点非常
严重,因为在网速非常慢的情况下,css下载时间比较长,这样就给用户带来“白屏”的时间自然也就很长了,用户体验非常差,经
常出现“无样式内容的闪烁”。
“无样式内容的闪烁”:样式表被(不正确地)放在了底部。当页面逐步加载时,文字首先显示,然后是图片。最后,在样式表正
确地下载并解析之后,已经呈现的文字和图片要用新的样式重绘了,这就是“无样式内容的闪烁”。
在上面文章中已经提到,web页面中将CSS放在底部会导致载网速低的情况下页面逐渐呈现阻塞,导致“白屏”,应该将CSS引用放在
顶部。而对于JavaScript文件,相反,应该放在底部。
JS文件放置在底部的原理
HTML中script有两个属性,async是异步执行,表示下载完js马上异步执行js,不阻塞浏览器呈现。defer表示延迟执行,需要等页面资
源下载完成后执行,相当于放在尾部。既没有async和defer的属性,页面将在下载后阻塞呈现立即执行。都没有使用到defer和async的
情况下JavaScript文件放在顶部会导致页面呈现阻塞。
脚本放在顶部会导致页面呈现阻塞,在网速慢的时候导致“白屏”,直到脚本下载完毕,页面才继续呈现。脚本放在底部则可以让
页面尽快呈现。
JS阻塞特性:
JS 有个阻塞特性,就是当浏览器在执行JS 代码时,不能同时做其他任何事情,无论其代码是内嵌的还是外部的。
原因是:JavaScript是单线程,在JavaScript运行时其他的事情不能被浏览器处理。事实上,大多数浏览器使用单线程处理UI更新
和JavaScript运行等多个任务,而同一时间只能有一个任务被执行。所以在执行JavaScript时,会妨碍其他页面动作。
这是JavaScript的特性,我们没法改变。
1、浏览器解析html时,是否解析到</html>后,dom树构造完成,触发Dom load事件,整个htmldom文档就是一个可用的状态,
此时document已经被关闭了,调用document.write会刷白整个页面。
2、经过测试,ie8+,ff,chrome,opera都是下载完html后,接着并行下载css和多个js,即使将script放在页面最后</body>之前也是如
此,已经不再是以前说的js一个一个下载。放在页面最后</body>之前的js,下载期间如果是异步的,不会阻塞页面渲染,但是每个
javascript执行的时候还是同步的,就是先出现的script标签一定是先执行,即使是并行下载它最后一个下载完成。除非标有defer的
script标签,否则任何javascript在执行的时候都会中断当前html文档解析,自然会阻止页面 渲染。
3、浏览器并行下载js和异步下载js其实没什么性能上的区别,只是异步下载javascript可以尽快的让用户看到页面,用户体验较好。
4、在《高性能javascript》中提到,使用JavaScript动态生成script节点,然后将其插入到文档中动态加载js,js加载是异步的,加载和
执行不会影响页面渲染!特别不理解,不是说只要执行js就阻塞页面渲染吗?
答:javascript加载是不会影响已经渲染的页面,但是会中断html文档解析,浏览器会在javascript执行以后决定当前文档是否需要
进行重新渲染或者文档重排。所以即使javascript放到最后面也会使浏览器暂停,但不影响之前已经解析出来的dom文档,此时对于用
户来说是可操作的,而在之前就加载javascript,浏览器会提早暂停,给用户看到的就是白色的页面,不友好。javascript有可能会修改
dom,此时修改dom可能比较危险,因为正处于不稳定的状态,如果使用document.write甚至会迫使浏览器强制解析新出现的动态内
容,如果是这种情况,javascript执行完成以后就会进行重排。如果javascript修改了css,影响了layout的话,也会进行重渲染或重排。
函数重名导致的调用出错(一定要避免函数重名啊!!!):
1、a.jsp页面中定义了一个check()方法;工程下还有一个common.js文件,其中也定义了一个check()方法;
2、在a.jsp中引入了common.js文件;
当出现一下情况时,会导致方法引用出错:
1、在a.jsp页中想要使用a.jsp文件内自定义的check()方法时:
1.1、引入common.js的时机是在自定义check()之后引入的,当调用的时候就会调用common.js中的check()方法。
1.2、引入common.js的时机是在自定义check()之前引入的,但是将common.js延迟执行(defer)或者异步执行(async)。也会出
现调用出错!
原因解析之Javascript中的函数参数机制:
一个函数有多少个参数不是通过函数的定义判断的,而是通过访问函数时传递了多少个参数判断。
比如functiona(x,y),无论括号里写多少个参数都无效,要想传入参数要在访问时确定,a(1,2,3);这样就是
传入了三个参数。因此functiona(x,y)、function a()、functiona(x)等写法是没有任何区别的,函数唯一
的区别就是名字,不通过参数区别。细心的读者可能会发现,假如有一个functiona(x,y),访问方法:
a(1,2,3),问题来了:我们可以在function中使用x、y形参获得1、2两个实参值,但是第三个参数怎么使用?
在js中,每一个function,都会有一个arguments数组,这个数组专门用来存放参数,在本例中,数组的情况
是:arguments[0]=1、arguments[1]=2、arguments[2]=3。这样就不必通过形参访问参数了,直接访问
arguments数组即可!
JS覆盖规则:
当出现多个同名函数时,以最后一个定义的为准,无论在哪调用该函数(即使在最后一个定义之前调用),
都将调用最后一个!因为js是解释执行的语言,在执行前要把整个代码段扫描,因此可以在函数定义之前使
用函数。
CSS样式的规则:
CSS样式:内联式(在元素上)、嵌入式(在<style></style>标签内)、外部。
这三种样式是有优先级的,记住他们的优先级内联式> 嵌入式 > 外部式
但是嵌入式>外部式有一个前提:嵌入式css样式的位置一定在外部式的后面。如右代码编辑器就是这样,
<linkhref="style.css" ...>代码在<styletype="text/css">...</style>代码的前面(实际开发中也是这么
写的)。
其实总结来说,就是--就近原则(离被设置元素越近优先级别越高)。
但注意上面所总结的优先级是有一个前提:内联式、嵌入式、外部式样式表中css样式是在的相同权值的情况
下。
规则6 -避免使用CSS表达式
最早的CSS是不支持所谓的表达式的,微软的IE从5.0开始引入了这种概念,意思是希望我们拥有定义动态的CSS样式的能力。但是不光
是除了IE之外的其他浏览器都不支持,同时w3c标准组织也不支持这种方式。鉴于此,微软方面也于2008年(彼时发布了IE8)的时候,
正式终止了对这种功能的支持。
所以,知道有这么回事就行了,别想他了,Just let it Go!。
规则7 -使JavaScript和CSS外部
通常情况下,JavaScript和CSS包含在外部文件中更好。但是纯粹而言,内联快一些。在下载量相同的情况下,内联比外部更快一些
(外部文件需要承担多个HTTP请求带来的开销,由此可见减少HTTP请求数量的重要性)。尽管外部示例可以从样式表和脚本的并行
下载中获益,但是HTTP请求数量的差距使内联示例在响应时间上更快一些。
但是现实中还是使用外部文件会产生更快的页面,外部文件所带来的收益是JavaScript和CSS文件有机会被浏览器缓存起来(如果
JavaScript和CSS是外部文件,浏览器就能缓存它们以减小HTML文档的大小并且不会增加HTTP请求的数量)。因此需要平衡(外部文件
需要多个HTTP请求带来的开销)与(引用外部文件并在浏览器缓存后带来的优点)。
规则8 -减少DNS查找
响应时间依赖于DNS解析器(通常由你的ISP提供),它所承受的请求压力,你与它之间的距离和你的带宽速度。
如何减少页面花在DNS查找上的时间很关键。主要技术手段有:DNS缓存和TTL.
DNS查找可以被缓存起来以提高性能,用户请求一个主机名之后,DNS信息会留在操作系统的DNS缓存中,之后对于该主机名的请求将
无需进行过多的DNS查找,至少短时间内不需要。
浏览器的缓存和操作系统的缓存相分离,只要浏览器在其缓存中保留了DNS记录,它就不会麻烦操作系统来请求这个记录。只有当浏览
器缓存丢弃这个记录的时候,它才会向操作系统询问地址,然后操作系统或者通过其缓存来响应这个请求,或者将请求发给一台远程
服务器,这时候就会发生潜在的速度降低。
规则9 -避免重定向
重定向用于将用户从一个URL重新路由到另一个URL。
常用重定向的类型:
301:永久重定向,主要用于当网站的域名发生变更之后,告诉搜索引擎域名已经变更了,应该把旧域名的的数据和链接数转移到新
域名下,从而不会让网站的排名因域名变更而受到影响。
302:临时重定向,主要实现post请求后告知浏览器转移到新的URL。
304:Not Modified,主要用于当浏览器在其缓存中保留了组件的一个副本,同时组件已经过期了,这是浏览器就会生成一个条件GET
请求,如果服务器的组件并没有修改过,则会返回304状态码,同时不携带主体,告知浏览器可以重用这个副本,减少响应大小。
重定向损伤性能:
当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载。
规则10 -使AJAX可缓存
对于AJAX而言,有一些特殊性,并不是所有的AJAX请求都是可以缓存的。这是由于AJAX的请求通常有两种不同的方法:POST和GET。
他们在进行请求的时候,就会略有不同。
POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。但是应用服务器缓存后
,服务器并不需要每次都运行真正的代码,它将结果缓存在内存中,在下次POST请求时,先判断缓存是否匹配并存在,若符合则重复
调用,就直接返回该内存中的数据即可;不符合则去数据库中读取,读取后将数据进行缓存,以便下次使用。(这样可以提高服务器
的性能,提高并发性)
GET的请求,是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的AJAX请求,不会重复在服务器执
行,而是返回304。
AJAX调用代码如下:
$("#btn").click(function () {
var name =$("#txtName").val();
//GET请求默认就是会被缓存(在同一个浏览器中,默认是临时缓存,浏览器一关闭就删除掉)
$.getJSON("HelloWCFService.svc/RestfulHelloWorldWithParameter",{ name: name }, function (data) {
alert(data.d);
});
});
运行起来之后,我们分别输入不同的name参数,并且分别调用两次。
我们可以发现,第一次调用会返回状态码(200),而第二次调用会返回状态码(304),但如果参数不一样,又会返回状态码(200)
。依次类推。
我们也确实可以在浏览器缓存中找到两份缓存的数据
所以对于GET请求,默认就会被缓存。但是,如果你想改变这个行为,例如你有时候不想做缓存,应该如何来实现
呢?
规则11 -配置Etag
HTTP协议规格说明定义ETag为“被请求变量的实体值”。另一种说法是,ETag是一个可以与Web资源关联的记号
(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含
义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
ETag:"50b1c1d4f775c61:df3";
客户端的查询更新格式是这样的:
If-None-Match : W / "50b1c1d4f775c61:df3";
如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。测试Etag主要在断点下载时比较有用。
Etag - Last-Modified和Etags如何帮助提高性能?
聪明的开发者会把Last-Modified和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生
Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器
验证其(客户端)缓存。
过程如下:
1.客户端请求一个页面(A)。
2.服务器返回页面A,并在给A加上一个Last-Modified/ETag。
3.客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
4.客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
5.服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
Etag-作用
Etag主要为了解决 Last-Modified无法解决的一些问题。
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修
改了,而重新GET;
2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,
这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP/1.1引入了 Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说
"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的
是Etag需要放在""内。
Etag-工作原理
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match.
请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).
状态码200
====第二次请求===
1.客户端发起 HTTP GET请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器
返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;
流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?
答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.(不要陷
入到底使用谁的问题怪圈)
Etag - Apache中Etag实现
1.Apache首先判断是不是弱Etag,这个留在下面讲。如果不是,进入第二种情况:
强Etag根据配置文件中的配置来设置Etag值,默认的Apache的FileEtag设置为:FileEtag INode Mtime Size;
也就是根据这三个属性来生成Etag值,他们之间通过一些算法来实现,并输出成hex的格式,相邻属性之间用-分隔,
比如:Etag"2e681a-6-5d044840";
这里面的三个段,分别代表了INode,MTime,Size根据算法算出的值的Hex格式,(如果在这里看到了非Hex里面的字符(也就是0-f),
那你可能看见神了:))
当然,可以改变Apache的FileEtag设置,比如设置成FileEtagSize,那么得到的Etag可能为:Etag"6";
总之,设置了几个段,Etag值就有几个段。(不要误以为Etag就是固定的3段式).
说明:
这里说的都是Apache2.2里面的Etag实现,因为HTTP/1.1并没有规定Etag必须是什么样的实现或者格式,因此,也可以修改或者完全编
写自己的算法得到Etag,比如"2e681a65d044840",客户端会记住并缓存下这个Etag(Windows里面保存在哪里,下次访问的时候直接拿
这个值去和服务器生成的Etag对比。
注意:
不管怎么样的算法,在服务器端都要进行计算,计算就有开销,会带来性能损失。因此为了榨干这一点点性能,不少网站完全把
Etag禁用了(比如Yahoo!),这其实不符合HTTP/1.1的规定,因为HTTP/1.1总是鼓励服务器尽可能的开启Etag。
Etag -弱校验(弱Etag)
重新考虑前面提到的3个问题:
问题1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件
被修改了,而重新GET;
解决办法:如果使用强Etag,每次得会要求重新GET页面,如果使用Etag,比方说设置成 File Etag Size 等,就可以忽略 MTime造成的
Last-Modified时间修改从而影响了 If-Modified-Since(IMS)这个校验了。这点和弱Etag无关。
问题2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级
的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
解决办法:如果是这种情况,Apache会自动判断请求时间和修改时间之间的差值,如果小于1s,Apache会认为这个文件在这1秒内
可能会再次被修改,因此生成一个弱Etag(WeakEtag),这个Etag仅仅基于MTime来生成,因此MTime只能精确到s,所以1s内生成的Etag
总是一样,这样就避免了使用强Etag造成的1s内频繁的刷新Cache的情况。(貌似不用Etag,仅仅使用Last-Modified就可以解决,但是
这针对的仅仅是修改超级频繁的情况,很多文件可能同时也使用强Etag验证)。弱Etag以W/开始,比如:W/"2e681a"
问题3、某些服务器不能精确的得到文件的最后修改时间;
解决办法:生成Etag,因为Etag可以综合Inode,MTime和Size,可以避免这个问题
规则12 -精简JavaScript、删除重复的脚本
精简:从javascript代码中移除所有的注释以及不必要的空白字符(空格,换行和制表符),减少javascript文件的大小。
混淆:和精简一样,会从javascript代码中移除注释和空白,另外也会改写代码。作为改写的一部分,函数和变量的名字将被转换为
更短的字符串,所以进一步减少了javascript文件的大小。
混淆的缺点:
1.缺陷:混淆过程本身很有可能引入错误。
2.维护:由于混淆会改变javascript符号,因此需要对任何不能改变的符号进行标记,防止混淆器修改它们。
3.调试:经过混淆的代码很难阅读,这使得在产品环境中更加难以调试。
相对而言,精简出错的概率会少很多。
一个精简和混淆的示例:
这个示例将使用JSMin进行精简,使用yuicompressor进行混淆。原始js如下:
functionshow(name, day) {
alert(name);
alert(day);
}
functiontest(name, day) {
var variable = name;
show(name, day);
}
JSMin精简后的代码:
[html]view plain copy
functionshow(name,day){alert(name);alert(day);}
functiontest(name,day){var variable=name;show(name,day);}
yuicompressor混淆后的代码:
[html]view plain copy
functionshow(b,a){alert(b);alert(a)}function test(c,a){var b=c;show(c,a)};
可见,混淆更能减少js代码的大小。
对精简和混淆进行抉择
我们知道启用gzip压缩能减少组件的传送大小,压缩后精简和混淆的差别会进一步减少,综合考虑混淆可能带来的额外的风险,
所以优先考虑使用精简。不过,如果对于性能的极致追求,可以使用混淆,但要做足测试,确保混淆不会带来其他的问题。
jQuery作为非常流行的前端框架,除了有开发版外,也提供了一个min版本,供实际部署web使用,这个min版本就使用了混淆,
最大化地减少代码总量。
移除重复脚本
出现重复脚本的原因
导致一个脚本的重复又两个主要因素:团队大小和脚本数量。开发一个网站需要极大数量的资源,不同的团队需要构建一个大型
web的不同部分,当团队整合和沟通工作没有做足,则容易出现重复脚本的情况。当然脚本数量也是重要的一环,脚本数量越多越
容易出现重复脚本的情况。
重复脚本如何损伤性能
在没有缓存的情况下,如果在html中重复链接了相同的脚本,IE7以下(包括IE7)将会产生两次HTTP请求,IE8以上则不会。除了产生
不必要的HTTP请求外,对脚本进行重复执行也会浪费时间,脚本的重复执行在浏览器中都存在。
如何避免重复脚本
1.形成良好的脚本组织。重复脚本有可能出现在不同的脚本包含同一段脚本的情况,有些是必要的,但有些却不是必要的,所以需要
对脚本进行一个良好的组织。
2.实现脚本管理器函数。
以上纯属个人查阅相关文档及相关网络资料后的整理总结,若有错误,望不吝指教,谢谢~