其实这篇文章昨天已经在csdn社区发表过,特意转帖过来,也相当于一个笔记吧, 分享给园子里的朋友.
http://topic.csdn.net/u/20100105/11/382a3b43-e0a7-4adf-aa91-bf6ff7e25539.html
个人觉得这些东西了解后,对web性能优化方面是有很大好处的.
前段时间接到腾讯的电话面试,题目大概是这样:
从输入一个新的网址到用户完全看到页面,这期间浏览器做了哪些工作?
输入网址后浏览器是先读取缓存文件夹还是先连接服务器?
.....
其实我个人觉得他问这个题重点是考浏览器缓存机制,资源下载序,css渲染与js执行顺序.
这个问题其实听起来是个非常基础的题目,但是感觉自已还是很模糊,所以本人在网上找了些资源,再加上自已的一些测试,将结果分享给大家,说错的地方希望大家指正批评.
ps:以下的分析只是针对浏览器缓存,解析方面,至于dns解析,底层的处理,tcp通信方面不在此范围.
HTTP协议基于请求响应模式,客户端向服务器发送一个请求,请求头包含请求的方法,URI,协议版本以及包含请求修饰符,
客户端信息和内容的类似 MIME的消息结果。服务器则以一个状态行为作为响应,相应的内容包括消息协议的版本,
成功或错误编码加上包含服务器信息,实体元信息以及可能的实体内容。
缓存:
当浏览器第一次请求某个URL时,顺利访问的话,服务器返回状态200的状态,
同时会返回给浏览器一些Headers集合,例如set-cookie,Last-Mondified,Etag等等
下面重点说明Last-Mondified与Etag,即浏览器缓存.
HTTP/1.x 200 OK
Server: Microsoft-IIS/7.5
Last-Modified: Thu, 31 Dec 2009 09:29:09 GMT
Etag: "e46de5b4fb89ca1:0"
Expires: Thu, 07 Jan 2010 00:00:00 GMT
Last-Mondified: 最后一次修改时间
Etag: 资源的状态唯一标识(每个资源的etag都不同,例如img,js,css。。。。)
Expires: 指定资源在浏览器缓存中的过期时间 (需要在服务端设定)
浏览器接收到服务器这些信息后,就会将资源缓存在本地目录中,同时保存文件的上述信息.
如果有set-cookie的话,且浏览器未禁用cookie,则会保存cookie信息,当cookie过期时间大于当前时间时,浏览器会将cookie保存在本地硬盘.
下次发送时会同header头一起发送给服务器,当然条件是相同域,path约束相符等等情况下.
第二次请求时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 与 If-None-Match 报头,
这两个报头实际上是第一次请求时服务器返回的Last-Modified,Etag.发送这两个报头目地是询问服务器,该资源在时间内有没有被修改过.
如果该资源未被修改,则服务器会直接返回HTTP 304 (Not Changed.)状态码,内容为空,此时不会下载资源,浏览器则自动从缓存目录中读取资源.
使用Last-Modified/Etag 可以减少传输成本,但不会减少http请求
测试结果截图(来自互联网):
如果给文件加上关于过期时间(Expires)的header报文,这样浏览器就会先检查缓存中的文件,如果没有过期,就直接使用缓存中的文件,从而不会发送http请求.
(只会请求一次主文档)
测试结果截图(来自互联网):
前面描述的只是一些普通的浏览器缓存状态,在实际应用中,如页面跳转(点击页面链接跳转,window.open,在地址栏敲回车,刷新页面)等操作,会有一些区别
普通页面跳转包括链接点击跳转,用js脚本打开新页面(window.open),iframe时
第一次请求服务器返回200,并返回资源的Last-Modified/Etag,
第二次请求时,浏览器发送上次接收的Last-Modified/Etag,服务器直接返回304(HTTP/1.x 304 Not Modified)
如果设置了Expires,且未过期,浏览器直接从缓存目录中读取,不发送请求给服务器
F5刷新时
与普通请求区别在于,即便资源设置了Expires且未过期,浏览器也会发送相应请求,然后根据服务器返回状态来决定是否下载资源.
Ctrl+F5刷新时与无缓存时效果一样,服务器返回200(资源全部重新下载).
其实明白上述的原理后,我们可以对web服务器header头进行合理的设置,从而可以大大提高性能.
以iis7为例,iis6设置差不多(iis管理--httpheaders选项卡--选择允许内容过期).
例如网站下有一个images文件夹,里面放着很多较大的图片,但这些图片最少1天之内不会被更改.
那么,我们可以给这个文件夹设置过期时间.
在iis7下切换至该文件夹的功能视图--选择iis类别中的http响应标头--设置常用标头--使web内容过期
测试:
环境:Live Http Headers + Firefox 3.5.6
在html文当中包含5张该文件夹下的图片.
第一次请求时,发送了6次http请求,6次下载(文档+5张图片)
第二次请求时,只发送了一次http请求,1次下载(文档,而图片则是直接从浏览器缓存中读取)
如果F5刷新,发送了6次http请求,对于5张图片,服务器返回HTTP/1.x 304 Not Modified,所以浏览器不会下载图片.总共1次下载.
下载资源顺序
主文档当然是第一个下载,其次
IE6是按html中定义的文档流顺序来下载外部资源,从上至下.
FF则略有不同,FF会优先下载js或css,而图片资源延迟到后面下载.
(经测试IE7,IE8也是先下载js或css,其它资源延后下载)
测试结果:
文档:
<HEAD>
<TITLE> New Document </TITLE>
<link href="main.css" rel="Stylesheet" />
<script type="text/javascript" src="/AreaCounter.js"> </script>
</HEAD>
<BODY>
<div>
<img src="csdnindex_piclogo.gif" />
<link href="index090703.css" rel="Stylesheet" />
<img src="475x60ttt_2.jpg"/>
<script language='JavaScript' type='text/javascript' src='/csdn_ggmm.js'> </script>
</div>
</BODY>
</HTML>
IE7/IE8/FF下载顺序:
渲染或解析(非DOM)
对于 js 运行,以及页面加载相关事件的触发,特别做了测试。在 Firefox 下,打开测试页面:
(以下测试数据来自互联网)
[22:13:32.947] HTML Start
[22:13:32.947] normal inline script run time
[22:13:34.904] normal external script run time
[22:13:35.775] [body] normal external script run time
[22:13:35.789] [body end] normal external script run time
[22:13:35.789] HTML End
[22:13:35.791] deferred inline script run time
[22:13:35.791] deferred external script run time
[22:13:35.793] DOMContentLoaded
[22:13:38.144] images[0] onload
[22:13:38.328] images[1] onload
[22:13:39.105] images[2] onload
[22:13:39.105] images[3] onload
[22:13:39.106] window.onload
很明显,JS 的运行严格按照文档流中的顺序进行。其中 deferred 的脚本会在最后运行(注:Firefox 3.5 开始支持 defer,而且支持得很完美)。
再来看下 IE8,结果如下:
[22:33:56.806] HTML Start
[22:33:56.826] normal inline script run time
[22:33:57.786] normal external script run time
[22:33:57.812] deferred inline script run time
[22:33:57.816] document.readyState = interactive
[22:33:57.934] [body] normal external script run time
[22:33:58.310] [body end] normal external script run time
[22:33:58.310] HTML End
[22:33:58.346] deferred external script run time
[22:33:58.346] images[0].readyState = loading
[22:33:58.346] images[0].readyState = complete
[22:33:58.346] images[0] onload
[22:33:58.361] doScroll
[22:33:58.451] images[1].readyState = loading
[22:33:58.479] images[1].readyState = complete
[22:33:58.479] images[1] onload
[22:33:58.794] images[2].readyState = loading
[22:33:58.854] images[2].readyState = complete
[22:33:58.854] images[2] onload
[22:33:58.876] images[3].readyState = loading
[22:33:58.876] images[3].readyState = complete
[22:33:58.876] images[3] onload
[22:33:58.887] document.readyState = complete
[22:33:58.888] window.onload
可以看出,IE8 下,defer 只对 external 脚本有效,对 inline 脚本无效。
css下载和渲染几乎是同时进行的.即下载完成后会立即渲染到页面.
当某一脚本下载完成时,也会立刻解析和运行。脚本的运行严格按照文档流中的顺序进行,deferred 的脚本会在正常脚本运行之后运行,
所以特别注意js脚本放置顺序.如果第二个外部脚本中直接调用运行第一个外部脚本中的全局变量时,会产生脚本错误.
特别需要留意:脚本运行时,会暂停该脚本之下所有资源的下载(因为脚本可能改变文档流,甚至跳转页面,浏览器的暂停策略是合理的)。
至于DOM的渲染是根据浏览器的渲染引擎来决定的.
这里可以了解各个浏览器的渲染引擎 :http://www.mac52ipod.cn/post/Trident-Gecko-WebKit-Presto.php
ps:defer是个很有用的东西,当某个脚本被标识为defer=true时,浏览器下载完该脚本后,不会立即执行该脚本,而是对其它资源进行下载和解析.
默认情况下,defer为false,所以尽量不要在标识defer的js块或外部js文件中使用全局变量.
例子
<script type="text/javascript">
alert(a);
</script>
<script type="text/javascript">
var a=3
</script>
该脚本会报a未定义的错误,如果将第一个脚本块增加 defer=true标记,即可正常运行.所以说加上 defer 有点类似于 window.onload,但比onload灵活!
<script type="text/javascript" defer> //等同于defer=true
alert(a);
</script>
defer目前好像只支持 ie系列,ff3.5+浏览器(其它未测),所以使用时请注意兼容性问题