如果不用xmlhttp方式獲取json數(shù)據(jù),一般我們最好用的方式是用script標(biāo)簽直接引用需要的腳本。但是不像xmlhttp可以很容易的把請(qǐng)求數(shù)據(jù)腳本和請(qǐng)求到的數(shù)據(jù)綁定到一起,script標(biāo)簽本身是無法獲知自己獲得了什么數(shù)據(jù)的,這個(gè)問題上一般使用的解決方案有:
1 事先約定前后臺(tái)接口。這樣帶來了很強(qiáng)的前后臺(tái)偶合,后臺(tái)程序需要知道前臺(tái)想要做什么,接口很難一致化,一般不同的服務(wù)程序要使用不同的接口。而且如果需要同時(shí)并發(fā)調(diào)用同一個(gè)服務(wù)程序幾次,那么一樣無法解決接口沖突問題。
2 前臺(tái)動(dòng)態(tài)生成回調(diào)接口后把接口名稱傳遞給后臺(tái)程序,后臺(tái)程序根據(jù)接受到的接口名稱動(dòng)態(tài)生成回調(diào)接口,比如google就喜歡接受callback參數(shù):
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
這樣也是一個(gè)無奈之舉,一樣避免不了的令人生厭的前后臺(tái)偶合,只是改變了偶合的方式,前后臺(tái)需要換一種方式的約定,而且如果要解決并行多個(gè)異步回調(diào)的接口沖突問題,就要?jiǎng)討B(tài)的給每個(gè)回調(diào)函數(shù)創(chuàng)建一個(gè)個(gè)不同的名稱,此外服務(wù)程序的輸出不允許靜態(tài)化,必須有接受參數(shù)和生成回調(diào)腳本的功能。
假如我們想要像生成靜態(tài)rss(
http://api.fanfou.com/statuses/user_timeline.rss)文件一樣的生成靜態(tài)的json(
http://api.fanfou.com/statuses/user_timeline.json)又不希望或者不能使用xmlhttp來拉取json字符串,而想要用一致的callback接口來回傳數(shù)據(jù),那么怎么樣才能解決接口沖突問題呢?事實(shí)上只有做到這點(diǎn),json才能真正想xml一樣變成一個(gè)純粹的數(shù)據(jù)描述方式,擺脫對(duì)具體上下文程序的依賴,讓一個(gè)數(shù)據(jù)自由的被不同目的的頁面mashup。比如說,在一個(gè)頁面上用json結(jié)合腳本技術(shù),把來自不同網(wǎng)站的相同格式的json數(shù)據(jù)合并顯示到一個(gè)頁面上。
emu在這個(gè)問題上花費(fèi)過無數(shù)心血后最終還是放棄了,直到昨晚,舜子才終于有了突破:
<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>
如果需要支持錯(cuò)誤處理,就稍微麻煩一點(diǎn)了,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();//如果數(shù)據(jù)被cache,運(yùn)行到這一行的時(shí)候有可能回調(diào)已經(jīng)完成,窗口已經(jīng)關(guān)閉。
}
}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 = "無法連接到服務(wù)器";
}
}(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上運(yùn)行通過。
這里有幾點(diǎn)說明:IE其實(shí)也可以用iframe(試試強(qiáng)行給isIE變量賦false值),不過用iframe的缺點(diǎn)是phantom click(會(huì)發(fā)出一個(gè)頁面跳轉(zhuǎn)的小聲音)和throbber of doom(應(yīng)該是指小沙漏型的下載圖標(biāo)吧?)。
用document fragment的好處是避免了IE7默認(rèn)安全模式下面禁止ActiveX的問題。不過利用了IE的一個(gè)特點(diǎn):document fragment不append到document的dom里面的時(shí)候,也可以擁有自己的腳本運(yùn)行空間,可以用script標(biāo)簽發(fā)起請(qǐng)求。這樣用document fragment就可以比iframe使用更少的客戶端資源來完成操作。
雖然多個(gè)版本的IE都支持這個(gè)特性,但是emu還是認(rèn)為其他非IE瀏覽器的處理更為合理,為了防止將來萬一IE fix了這個(gè)bug造成措手不及,emu準(zhǔn)備了另外兩個(gè)備用方案,一個(gè)是當(dāng)useFragment被聲明為false的情況下,可以用一個(gè)htmlfile的控件來代替(google在gmail中使用了這個(gè)控件,但是造成一些用戶在抱怨IE7下面的安全提示);另一個(gè)是如果不能用ActiveX,還可以走非IE瀏覽器的邏輯,用iframe來完成操作,但是耗費(fèi)的客戶端資源要稍微多一點(diǎn)。用iframe另外兩個(gè)的缺點(diǎn)是phantom click(會(huì)發(fā)出一個(gè)頁面跳轉(zhuǎn)的小聲音)和throbber of doom(應(yīng)該是指小沙漏型的下載圖標(biāo)吧?)。針對(duì)具體的用戶群的瀏覽器種類,上面幾種方案不用全上,看需要了。
firefox下面的script標(biāo)簽其實(shí)支持onerror事件(可以寫在標(biāo)簽里面或者addEventListener上去),其他瀏覽器根據(jù)版本的不同對(duì)此有不同程度的支持,所以emu決定利用script標(biāo)簽可以堵塞頁面運(yùn)行過程的做法,script標(biāo)簽后面添加延遲的錯(cuò)誤處理邏輯(在正確的情形下?lián)屜惹蹇盏鬷frame的內(nèi)容來取消這個(gè)邏輯)。