如果不用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的內容來取消這個邏輯)。