/ JavaScript

预加载资源研究

什么是预加载

所谓预加载,就是通过一定的编程方法,使浏览器在空闲的时候,在后台通过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]是否可以确定何时完成预加载:由于预加载资源用时的不确定性,有可能导致用户在资源未加载完成时产生行为导致加载请求被中断。因此需要考察资源的加载完成是否可控,主要考察是否有loaderrorreadystatechange等事件。
  • [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]是否有 [F]是否可以删除标签
*:只实测了DOM元素被删除后的效果,因无法控制GC,未测元素对象被GC的情况。
[A] [B] [C] [D] [E] [F]*
script yes yes yes no yes yes
img no yes yes no yes yes
iframe yes yes no yes yes yes
XMLHttpRequest yes no yes yes yes yes
Flash yes no yes yes yes yes
背景图片 no yes yes no no yes
object/embed no yes no no yes yes
link + media yes yes yes yes no yes
@import yes yes yes yes no yes
Link Prefetch no yes yes yes no yes
WebWorker no yes no yes no yes
@font-face no yes yes yes no yes

从表格展现的数据来看,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吧。

参考资料