注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

学会珍惜

You've been saying for the longest time

 
 
 

日志

 
 

全新的第三方脚本提供方案:自更新脚本  

2013-02-06 14:23:04|  分类: 前端开发 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
转自:Front-end engineers

作者:steve souders
译者:heavenhuang
在使用 PAGE SPEED或者 YSLOW分析页面的时候,通常会因为第三方资源的短缓存问题拉低得分。第三方脚本的开发者使用短缓存的原因是因为他们需要及时的更新内容,虽然这样做会让主站的速度下降,但这也是不得而为之。
Stoyan 和我讨论起这个问题,是否能有一个方法让第三方脚本使用长缓存而需要更新时又能快速更新。我们想出了一个简单可靠的方法,采用这个模式可以减少不必要的的HTTP请求,速度更快,YSLOW评分更高。

长缓存和修改URL

缓存是减轻网站负载的重要实践,(如果你已经熟悉 cache和304,你可以直接跳过本章直接阅读 自更新部分)
长缓存非常容易实现 ,你只需要在 HTTP响应头中设置较长的过期时间例如:
Cache-Control:max-age = 31536000
但是问题在于当你想要更新内容时,怎么办?缓存了旧版本的用户需要等待资源过期才会获取新的资源,如果你按上面 进行了设置,那用户有可能需要等待1年才更新。那现实中,我们是通过修改URL指纹路径,版本号、文件时间戳、或者校验等方法控制资源及时更新的,以下是 facebook 的例子:
http://static.ak.fbcdn.net/rsrc.php/v1/yx/r/N-kcJF3mlg6.js
你可以看到类似的大网站的资源文件地址随着时间一直在变化
http://static.ak.fbcdn.net/rsrc.php/v1/y2/r/UVaDehc7DST.js (March 1)
http://static.ak.fbcdn.net/rsrc.php/v1/y-/r/Oet3o2R_9MQ.js (March 15)
http://static.ak.fbcdn.net/rsrc.php/v1/yS/r/B-e2tX_mUXZ.js (April 1)
http://static.ak.fbcdn.net/rsrc.php/v1/yx/r/N-kcJF3mlg6.js (April 15)
Facebook的这个脚本设置了1年的长缓存,当需要更新的时候他们是通过改变地址以确保所有用户都能立即拿到最新的内容。设置长缓存和修改地址是解决这个问题的好办法,但不幸的是并不适合在第三方脚本的情况下使用。

第三方脚本的问题

修改资源文件地址是一个简单的解决方案,这需要网站开发者知道什么时候修改地址,而第三方脚本什么时候更新,你是不知道的,所以不能够这样做。大多数第三方脚本都会有一个引导脚本的JS,例如 twitter的widget.js:
<a href="https://twitter.com/share" class="twitter-share-button"
data-lang="en">Tweet</a>
<script>
!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)){
js=d.createElement(s); js.id=id;
js.src="//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}}(document,"script","twitter-wjs");
</script>
网站开发者把这段代码复制到自己的页面中,在需要紧急更新时,TWITTER的团队无法修改引导脚本中的 widgets.js的网址,也无法及时通知所有的网站开发者。因此第三方脚本通常会设置较短的缓存时间以确保用能能够及时拿到最新的文件。 TWITTER 的WIDGETS.JS缓存 30分钟, FACEBOOK的 ALL.JS缓存15分钟,GOOGLE ANALYTICS的 GA.JS 缓存 2小时。这远远高于YSLOW一个月或者更多的建议做法。

被损害的性能

这些第三方脚本的对网络性能有负面影响,当片段的缓存过期时会发起一个新HTTP请求,如果内容没有改变则返回304虽然304响应是没有内容的,但仍然有网络传输的代价,而传输的时间将会影响用户体验,影响有多大取决于第三方脚本采取的是普通方式加载还是异步加载。
普通加载方式指的是 在HTML代码中加入<script src = "xxx.js"></a>。这种加载方式有一些负面的影响:它会阻塞后续的DOM渲染,在老版本的浏览器中还会阻塞后续的资源加载。 那怕返回是304,这个请求在网络传输所花费的时间都会造成后续资源的阻塞。
异步加载是为了解决上述的问题,但这种情况的缺点主要是它不能和页面同步输出,往往页面其他部分先加载完这部分内容后展示
理想状态下应该是通过普通方式加载,但所请求的内容能够从缓存中取得,并且缓存时间足够长,问题在于怎么做?

自更新脚本

首先我们对第三方脚本加载方案提出了一些要求 :它所引用的js地址是不能被及时修改的,我们希望给这个js设置较长的缓存时间,同时又能及时更新。这里解决两个主要问题:

通知更新

下面的例子将以tweet按钮脚本为例进行改造,我们向服务器添加:1个被iframe的页面,两个beacon.js。在bootstrap.js中加入了一行版本号。通过beacon.js的返回内容控制 bootstrap.js什么时候需要获取新的版本。

替换本地缓存

这是问题比较棘手,如果我们设置了一个较长的缓存时间,我们需要在它尚未过期的时候替换掉他。我们可以动态的取 原有的脚本,但浏览器会从缓存中返回,而如果我们通过给请求加上时间戳则会形成新的缓存而不会替代原有缓存中的脚本。我们尝试通过XHR修改 setRequestHeader的方式干预缓存,但并不是所有浏览器都有效。
Stoyan 想到了一个新点子:由服务器通知客户端动态创建一个iframe,iframe中包含我们需要更新的js,然后重载这个页面。
location.reload(true); //强制从服务器取得新的内容
利用这一特性,我们实现了之前提到的两个目标:设置长缓存同时保有实时更新的能力。比旧的方法我们已经取得了很多的进步,例如30分钟缓存周期的widgets.js原本情况是用户每30分钟都要发起一个新请求,而现在只有在 widgets.js有更新时才会发起请求。

具体的例子 点击访问

以GOOGLE ANALYTICS 为蓝本编写了这个bootstrap.js的例子。这个例子包含4个页面:
第一页:对bootstrap.js进行加载(目的是建立缓存)
(function() {
var s1 = document.createElement('script');
s1.async = true;
s1.src = 'http://souders.org/tests/selfupdating/bootstrap.js';
var s0 = document.getElementsByTagName('script')[0];
s0.parentNode.insertBefore(s1, s0);
})();
注意:例子中站点域名是 stevesouders.com,但bootstrap.js 是从 souders.org域名获得的,这样能够更好的模拟实际工作中第三方脚本跨域的情况。在测试中bootstrap.js设置缓存一周,并包含版本号 (实际上是时间戳),该时间戳将会显示在页面中,例如:16:23:53。同时bootstrap.js会向服务器发起一个beacon.js,此时响应 为空204。
第二页:为了确认我们正常使用缓存,所以再一次发起对bootstarp.js的请求,可以看到是从缓存中读取的,取到的时间戳也是相同的:16:23:53。同样的发起一个beacon.js的请求,。依然响应为空204
第三页:见证奇迹的时候来了,再次发起bootstarp.js请求,第一次从缓存中取得,但是当beacon.js从服务器返回时通知有新版本需要同步。这是beacon.js的返回内容:
(function() {
var doUpdate = function() {
if ( "undefined" === typeof(document.body) || !document.body ) {
setTimeout(doUpdate, 500);
}
else {
var iframe1 = document.createElement("iframe");
iframe1.style.display = "none";
iframe1.src = "http://souders.org/tests/selfupdating/update.php?v=[ver #]";
document.body.appendChild(iframe1);
}
};
doUpdate();
})();
beacon.js 新增的iframe指向 update.php。
<html>
<head>
<script src="http://souders.org/tests/selfupdating/bootstrap.js"></script>
</head>
<body>
<script>
if (location.hash === '') {
location.hash = "check";
location.reload(true);
}
</script>
</body>
</html>
update.php有两个任务,一个是重载iframe,另外一个是写入新的缓存。通过对hash锚点添加一个标记,避免无限循环。通过下面的序列图我们可以很清楚的看到效果:
全新的第三方脚本提供方案:自更新脚本 - 听歌 - 学会珍惜
 
页面newver.php 从缓存中读取了bootstrap.js, beacon.js的返回脚本中新建了一个隐藏IFRAME,对update.php发起了请求,update.php重载了一次,此时新的bootstrap.js 覆盖了旧有的缓存。
第四页:再次访问bootstrap.js,缓存已经更新了!

后记

这个方法的效果是让用户下一个访问缓存能得到更新。我们看到在关键的第三页中老版本的bootstrap.js 被后来下载的新版本所覆盖。与缓存时间短或者多次请求的异步加载的典型行为相比,这个方法缓存更新更及时有效。另外一种策略是始终检查更新,通过 bootstrap.js本身实现重载,缺点是这将大大增加请求数。需要指出的是,这里的update.php并不需要时一个动态的页面,我们可以同一个 有长缓存的静态HTML页面来完成此项工作。update.php实际可以拓展为一个检查清单,用它来管理包括bootstrap.js在内的其他脚本。 只需要对传入的地址参数进行判断,找出需要检查的脚本和版本号即可。这个方法的另外一个好处是对现有的脚本都无需大幅改造。作为第三方脚本提供方的你需要 做的只是:
1、为bootstrap.js增加一行版本号。
2、通过其他一些请求返回服务器端的版本号。
3、修改该请求的返回内容为 通过JS加载上面提到的ifame和updata.php
4、在自己的服务器上增加update.php页面,在当中加上你需要维护的资源列表。
5、增加bootstrap脚本的缓存时间。
如果你有这么一段脚本,我鼓励你通过这种全新的方式进行自我更新,为了用户体验和减少请求!
  评论这张
 
阅读(179)| 评论(0)
推荐

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017