如果不用xmlhttp方式获取json数据,一般我们最好用的方式是用script标签直接引用需要的脚本。但是不像xmlhttp可以很容易的把请求数据脚本和请求到的数据绑定到一起,script标签本身是无法获知自己获得了什么数据的,这个问题上一般使用的解决方案有:
1 事先约定前后台接口。这样带来了很强的前后台偶合,后台程序需要知道前台想要做什么,接口很难一致化,一般不同的服务程序要使用不同的接口。而且如果需要同时并发调用同一个服务程序几次,那么一样无法解决接口冲突问题。
2 前台动态生成回调接口后把接口名称传递给后台程序,后台程序根据接受到的接口名称动态生成回调接口,比如google就喜欢接受callback参数:
http://www.google.com/reader/public/javascript/user/10949413115399023739/label/officialgoogleblogs?n=10&callback=test饭否的接口也是这样的:
http://api.fanfou.com/statuses/user_timeline.json?callback=test
这样也是一个无奈之举,一样避免不了的令人生厌的前后台偶合,只是改变了偶合的方式,前后台需要换一种方式的约定,而且如果要解决并行多个异步回调的接口冲突问题,就要动态的给每个回调函数创建一个个不同的名称,此外服务程序的输出不允许静态化,必须有接受参数和生成回调脚本的功能。
假如我们想要像生成静态rss(
http://api.fanfou.com/statuses/user_timeline.rss)文件一样的生成静态的json(
http://api.fanfou.com/statuses/user_timeline.json)又不希望或者不能使用xmlhttp来拉取json字符串,而想要用一致的callback接口来回传数据,那么怎么样才能解决接口冲突问题呢?事实上只有做到这点,json才能真正想xml一样变成一个纯粹的数据描述方式,摆脱对具体上下文程序的依赖,让一个数据自由的被不同目的的页面mashup。比如说,在一个页面上用json结合脚本技术,把来自不同网站的相同格式的json数据合并显示到一个页面上。
emu在这个问题上花费过无数心血后最终还是放弃了,直到昨晚,舜子才终于有了突破:
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function loadjs(url,callback){
if(window.ActiveXObject){
var df = document.createDocumentFragment();
df.visitCountCallBack = callback
var s = document.createElement("script");
df.appendChild(s)
s.src=url;
}else{
var i = document.createElement("IFRAME");
i.callbackID = "2";
i.style.display="none";
i.callback=callback;
i.src="javascript:\"<script>function visitCountCallBack(o){frameElement.callback(o)}<\/script><script src='"+url+"'><\/script>\""
document.body.appendChild(i);
i.contentWindow.callback = callback
}
}
function init(){
var spans = document.getElementsByTagName("span");
for(var i=0;i<spans.length;i++){
var id = spans[i].id;
var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
var callback = function(id){ return function(data){
document.getElementById(id).innerHTML = data.visitcount;
}
}(id);
loadjs(url,callback);
}
}
</SCRIPT>
</HEAD>
<BODY onload="init()">
123456 的访问量:<span id="123456"></span><BR>
2543061 的访问量:<span id="2543061"></span><BR>
20050606 的访问量:<span id="20050606"></span><BR>
</BODY>
</HTML>
如果需要支持错误处理,就稍微麻烦一点了,emu的做法是这样的:
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var isIE = !!window.ActiveXObject;
var useFragment=false;
function loadjs(url,callback,errcallback){
if(isIE){
if(useFragment){
var df = document.createDocumentFragment();
df.visitCountCallBack = function(data){
s.onreadystatechange=null;
df=null;
callback(data);
}
var s = df.createElement("SCRIPT");
df.appendChild(s);
s.onreadystatechange=function(){
if(s.readyState=="loaded") {
s.onreadystatechange=null;
df=null;
errcallback();
}
}
s.src = url;
}else{
var i=new ActiveXObject("htmlfile");
i.open();
i.parentWindow.visitCountCallBack=function(i){
return function(d){
i.parentWindow.errcallback=null;
i=null;
callback(d);
}
}(i);
i.parentWindow.errcallback=function(d){
i.parentWindow.errcallback=null;
i=null;
errcallback(d);
}
i.write("<script src=\""+url+"\"><\/script><script defer>setTimeout(\"errcallback()\",0)<\/script>")
if(i)i.close();//如果数据被cache,运行到这一行的时候有可能回调已经完成,窗口已经关闭。
}
}else{
var i = document.createElement("IFRAME");
i.style.display="none";
i.callback=function(o){
callback(o);
i.contentWindow.callback=null;
i.src="about:blank"
i.parentNode.removeChild(i);
i = null;
};
i.errcallback = errcallback;
i.src="javascript:\"<script>function visitCountCallBack(data){frameElement.callback(data)};<\/script><script src='"+url+"'><\/script><script>setTimeout('frameElement.errcallback()',0)<\/script>\"";
document.body.appendChild(i);
}
}
function init(){
var spans = document.getElementsByTagName("span");
for(var i=0;i<spans.length;i++){
var id = spans[i].id;
var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
var callback = function(id){ return function(data){
document.getElementById(id).innerHTML = data.visitcount;
}
}(id);
var errcallback = function(id){ return function(){
document.getElementById(id).innerHTML = "无法连接到服务器";
}
}(id);
loadjs(url,callback,errcallback);
}
}
</SCRIPT>
</HEAD>
<BODY onload="init()">
123456 的访问量:<span id="123456"></span><BR>
2543061 的访问量:<span id="2543061"></span><BR>
20050606 的访问量:<span id="20050606"></span><BR>
</BODY>
</HTML>
在IE/FIREFOX/OPERA/SAFARI上运行通过。
这里有几点说明:IE其实也可以用iframe(试试强行给isIE变量赋false值),不过用iframe的缺点是phantom click(会发出一个页面跳转的小声音)和throbber of doom(应该是指小沙漏型的下载图标吧?)。
用document fragment的好处是避免了IE7默认安全模式下面禁止ActiveX的问题。不过利用了IE的一个特点:document fragment不append到document的dom里面的时候,也可以拥有自己的脚本运行空间,可以用script标签发起请求。这样用document fragment就可以比iframe使用更少的客户端资源来完成操作。
虽然多个版本的IE都支持这个特性,但是emu还是认为其他非IE浏览器的处理更为合理,为了防止将来万一IE fix了这个bug造成措手不及,emu准备了另外两个备用方案,一个是当useFragment被声明为false的情况下,可以用一个htmlfile的控件来代替(google在gmail中使用了这个控件,但是造成一些用户在抱怨IE7下面的安全提示);另一个是如果不能用ActiveX,还可以走非IE浏览器的逻辑,用iframe来完成操作,但是耗费的客户端资源要稍微多一点。用iframe另外两个的缺点是phantom click(会发出一个页面跳转的小声音)和throbber of doom(应该是指小沙漏型的下载图标吧?)。针对具体的用户群的浏览器种类,上面几种方案不用全上,看需要了。
firefox下面的script标签其实支持onerror事件(可以写在标签里面或者addEventListener上去),其他浏览器根据版本的不同对此有不同程度的支持,所以emu决定利用script标签可以堵塞页面运行过程的做法,script标签后面添加延迟的错误处理逻辑(在正确的情形下抢先清空掉iframe的内容来取消这个逻辑)。