原文:
什么是预加载
所谓预加载,就是通过一定的编程方法,使浏览器在空间的时候,在后台通过HTTP请求访问某些资源。当用户在一段时间后真正使用这些资源的时候,相比一个完整的(返回200)的请求,可以更快地获得这些资源(返回304或者直接命中浏览器缓存)。
预加载在部分情况下有着十分重要的意义,特别是当确定某些资源用户在短时间内会使用,如分页列表的上一页和下一页、以及一些常用的LOGO之类的图片等。
预加载资源可能的方式
预加载的原理就是想办法发送一个HTTP请求,对响应的缓存等都由浏览器完成,因此一切有可能读取远程资源的方案都可以成为预加载资源的方案,大致有以下几类:
常规方式
- 使用script标签:
<script type="other/prefetch" src="some.res"></script>
- 使用img标签:
<img src="some.res" />
- 使用iframe标签:
<iframe src="some.res"></iframe>
- 使用XMLHttpRequest加载:
$.get('some.res');
- 使用Flash进行加载:需要编写特定的Flash
非常规方式
- 使用背景图片:
<div style="background-image: url(some.res);"></div>
- 使用object或embed标签:
<object type="other/prefetch" src="some.res"></object>
- 使用link标签并修改media:
<link rel="stylesheet" href="some.res" media="prefetch" />
- 在CSS中做import:
@import "some.res"
新一代方法
- 使用Link Prefetch:
<link rel="prefetch" href="some.res" />
- 使用WebWorker:
var worker = new Worker('some.res');
- 使用@font-face:
@font-face { font-family: prefetch; src: url(some.res); }
方法有很多,也可能会有更多,具体的使用方式就不详细说明了,具体需要注意的细节会在后文详细描述。
预加载资源方式的评估指标
每一种方式或多或少都有其长处和缺点,本次主要按以下几个维度进行评估:
- [A]浏览器兼容性
- 主要考察包括IE6-8、Firefox3.5+、Chrome7+、Opera9+以及Safari4+的兼容性。
- [B]资源位置的覆盖性
- 主要考察是否有跨域政策的限制,是否能读取第三方的资源。
- [C]引入第三方资源的安全性
- 主要考察是否会对加载的资源进行解析和执行,是否可能产生如XSS等安全问题。
- [D]引入资源类型的覆盖性
- 主要考察是否可以引入不同类型的资源,包括text、image、script、html等。
- [E]是否可以确定何时完成预加载
- 由于预加载资源用时的不确定性,有可能导致用户在资源未加载完成时产生行为导致加载请求被中断。因此需要考察资源的加载完成是否可控,主要考察是否有load、error、readystatechange等事件。
- [F]积累的标签的清理可行性
- 如果预加载资源的方法会引入多余的标签,如link、script等,需要考察在资源加载过程中,将对应的标签删除是否会导致请求中断。
评估中出现的问题
随着不断深入,各种方案的缺陷也被一点点挖掘,以下是一些不太容易注意到的奇怪的问题。
script标签
在Firefox下,当script标签的type属性是Firefox无法识别的脚本类型时,Firefox不会发送任何请求,基本上除了type="text/javascript"以外,Firefox都不予理睬。
img标签以及background-image
在Firefox下,当img标签或者background-image样式请求的内容返回的Content-Type不是image大类时,其响应体(Response Body)只会被接收1个包,其后的内容全部丢弃。
Flash
对于跨域的请求,Flash会先读取对方服务器上的策略xml文件,如策略文件允许跨域,则会进行加载。
object或embed标签
在Firefox下,无论元素是否隐藏,都会提示要求安装插件,当然这插件是找不到的。
在Chrome下,如果元素被隐藏,则不发起请求;元素未隐藏则提示安装插件。
在下载完插件前,所有浏览器都不会发起请求。
但如果object标签的type使用text/plain
则不存在安装插件的问题,不过悲剧的是在IE下不会发出请求,而在Firefox和Chrome下会解析执行HTML资源。
需要注意的是,object元素有其特殊性,创建一个object元素的代价远远大于其他元素。
link标签
在IE下有load事件,Opera中可以定时查看readystate以确定是否完成了请求,在其他浏览器中则不存在。
Link Prefetch
现阶段仅Firefox给予了支持,该功能在HTML5草案中,非常值得期待。
WebWorker
使用WebWorker加载的脚本文件会立刻被解析和执行,虽然在worker中的global对象是带有一定限制的,但依旧无法完全阻止第三方脚本注入有害的代码。
@font-face
@font-face仅在样式表中定义是不会发起请求的,必须创建一个元素,将其font-family设为该font-face,并且该元素必须被添加到DOM树中才会产生请求。
评估表格及基本判断
表格从前文所述的A-F共6个方面来考察各种预加载资源的方式,以期较为直观地去评价各种方式的优劣。
[A]浏览器兼容性 [B]是否跨域 [C]安全性 [D]资源类型 [E]是否有onload [F]是否可以删除标签 *:只实测了DOM元素被删除后的效果,因无法控制GC,未测元素对象被GC的情况。 |
||||||
[A] | [B] | [C] | [D] | [E] | [F]* | |
script | 1 | 1 | 1 | 0 | 1 | 1 |
img | 0 | 1 | 1 | 0 | 1 | 1 |
iframe | 1 | 1 | 0 | 1 | 1 | 1 |
XMLHttpRequest | 1 | 0 | 1 | 1 | 1 | 1 |
Flash | 1 | 0 | 1 | 1 | 1 | 1 |
背景图片 | 0 | 1 | 1 | 0 | 0 | 1 |
object/embed | 0 | 1 | 0 | 0 | 1 | 1 |
link + media | 1 | 1 | 1 | 1 | 0 | 1 |
@import | 1 | 1 | 1 | 1 | 0 | 1 |
Link Prefetch | 0 | 1 | 1 | 1 | 0 | 1 |
WebWorker | 0 | 1 | 0 | 1 | 0 | 1 |
@font-face | 0 | 1 | 1 | 1 | 0 | 1 |
从表格展现的数据来看,link+media的方式以及css @import方式都比较优秀,唯一的遗憾是无法获得其是否加载完成,因此需要站点自身通过研究用户的行为,保证用户2次操作的间隔足够完成资源的加载。
其他考虑
- Link Prefetch作为HTML5草案中的标准,且浏览器底层级别支持,因此浏览器可以在带宽空余地时候才进行预加载,用户也可以通过一定的方式关闭该功能,各方面都有不俗的表现,如果可以推广到主流浏览器,应该是作为最值得推荐的方案。
- @import需要服务器端辅助,将需要加载的资源打包成一个css格式的文件,文件中包含若干个@import声明,但是无论如何,浏览器会需要多一个请求用于加载这个动态生成的css文件。
- link+media的方式中,可以通过修改link的href属性,使用一个link加载多个资源,不会因为标签过多导致DOM结构的臃肿以至于影响性能。
- 在IE下,使用link+media的方式,无论下载来的内容是否被解析,IE会对页面进行一次redraw,这一次redraw的性能损失非常小,大致只是进入了redraw方法,但并没有真正地重新进行布局。
- object标签必须与DOM树相连才会加载资源,使用object标签时,在非IE浏览器下,可以将object标签的宽和高均设为0,但IE不行。在IE中可以将宽高均设为1,并使用
visibility: hidden; position: absolute; top: 0; z-index: -1000;
的样式来将其隐藏。当然既然IE不会发起请求,就怎么设也没意义了……
结论?
根据使用场景的不同,不会有一种万能的最佳解决方案,但大致可以总结如下:
- 加载同域资源,使用XMLHttpRequest即可。
- 加载第三方可信任的资源,如同公司内不同系统,可以使用iframe加载非HTML资源。
- 加载图片资源,使用img是最好的方式,但需要注意在请求过程中img不能被GC回收。
- 对于第三方的不可信任型资源,考虑使用link+media的方式加载,但无法确定加载完成的时间点。
- 如果可以判断浏览器,针对Firefox使用Link Prefetch,针对IE使用img标签,其他浏览器使用script标签算是一种较为完美的解决方案。
- 从未来看,Link Prefetch必将是大势所趋,大家给WHATWG提意见,让他加上onload吧。