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