讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議140:正確理解JSONP異步通信協議 >

建議140:正確理解JSONP異步通信協議

瀏覽器安全模型規定:XMLHttpRequest和框架(frame)等只能夠在同一個域內進行異步通信。受同源策略的限制,不能夠使用Ajax或框架實現跨域(即與外部服務器)通信。從安全角度考慮,這個規定很合理,不過也會給分佈式Web開發帶來麻煩。而JSONP是一種可以繞過同源策略的方法,從任何服務器端直接返回可執行的JavaScript函數調用(即回調函數)或JavaScript對像(JSON格式數據)。

JSONP是JSON with Padding的簡稱,它能夠通過在當前文檔(客戶端)中生成腳本(即<script>標籤)來調用跨域腳本(服務器端腳本文件),這是一個非官方的協議。

JSONP允許在服務器端集成Script Tags(腳本標記,即服務器動態生成的JavaScript腳本字符串)並把這些腳本標記返回到客戶端,通過JavaScript Callback(JavaScript回調函數)的形式實現跨域訪問。當然,這僅僅是JSONP簡單的實現形式。現在的一些JavaScript技術框架都在使用JSONP實現跨域異步通信,如dojo、JQuery、Google Social Graph API、Digg API、GeoNames webservice、豆瓣API、Del.icio.us JSON API等。下面通過一個示例介紹如何通過JSONP約定來實現跨域異步信息交互。

第1步,在客戶端調用提供JSONP支持的URL Service(URL服務),獲取JSONP格式數據。所謂JSONP支持的URL Service,就是在請求的URL中必須附加在客戶端的可以回調的函數,並且按約定正確設置回調函數參數,默認參數名為jsonp,偶爾也會見到以「callback」為參數名傳送回調函數名的情況。總之,只要服務器能夠識別即可。例如,定義URL Service為:


http://localhost/mysite/serve.ASP?jsonp=callback&d=1


其中參數jsonp的值為約定的回調函數名。JSONP格式的數據就是把JSON數據作為參數傳遞給回調函數並傳回。如果響應的JSON數據為:


{

\"title\":\"JSONP Test\",

\"link\":\"http://www.jscode.cn/\",

\"modified\":\"2012-4-1\",

\"items\":{

\"id\":1,

\"title\":\"百度\",

\"link\":\"http://www.baidu.com/\",

\"description\":\"百度一下,你就知道\"

}

}


那麼真正返回到客戶端的Script Tags(腳本標記)如下,即所謂的JSONP格式數據。


callback({

\"title\":\"JSONP Test\",

\"link\":\"http://www.jscode.cn/\",

\"modified\":\"2012-4-1\",

\"items\":{

\"id\":1,

\"title\":\"百度\",

\"link\":\"http://www.baidu.com/\",

\"description\":\"百度一下,你就知道\"

}

})


第2步,在客戶端向服務器端發出URL Service請求且服務器接到請求之後,應該完成兩件事情:一是接收並處理參數信息,特別是要獲取回調函數名;二是根據參數信息生成符合客戶端需要的Script Tags(腳本標記)字符串,並且把這些字符串響應給客戶端。例如,服務器端的處理腳本文件如下:


<%@LANGUAGE=\"VBSCRIPT\"CODEPAGE=\"65001\"%>

<%

callback=Request.QueryString(\"jsonp\")//接收回調函數名的參數值

id=Request.QueryString(\"id\")//接收響應信息的編號

Response.AddHeader\"Content-Type\",\"text/html;charset=utf-8\"//設置響應信息的字符編碼為uft-8

Response.Write(callback&\"(\")//輸出回調函數名,開始生成Script Tags字符串

%>

{

\"title\":\"JSONP Test\",

\"link\":\"http://www.jscode.cn/\",

\"modified\":\"2012-4-1\",

\"items\":

<%

ifthen//如果id參數值為1,則輸出下面的對象信息

%>

{

\"title\":\"百度\",

\"link\":\"http://www.baidu.com/\",

\"description\":\"百度一下,你就知道\"

}

<%

elseifthen//如果id參數值為2,則輸出下面的對象信息

%>

{

\"title\":\"搜狗\",

\"link\":\"http://www.sogou.com/\",

\"description\":\"上網從搜狗開始\"

}

<%

else//否則輸出空信息

Response.Write(\"\")

end if//結束條件語句

Response.Write(\"))\")//封閉回調函數,輸出Script Tags字符串

%>


包含在「<%」和「%>」分隔符之間的代碼是ASP處理腳本。在該分隔符之後的是輸出到客戶端的普通字符串。在ASP腳本中,使用Response.Write方法輸出回調函數名和運算符號。其中還用條件語句來判斷從客戶端傳遞過來的參數值,並根據參數值決定響應的具體信息。

第3步,在客戶端設計回調函數。設計回調函數應該根據具體的應用項目,以及返回的JSONP數據進行處理。例如,針對上面返回的JSONP數據,把其中的數據列表顯示出來,代碼如下:


function callback(info){

var temp=\"\";

for(var i in info){

if(typeof info[i]!=\"object\"){

temp+=i+\"=\"\"+info[i]+\"\"<br/>\";

}

else if((typeof info[i]==\"object\")){

temp+=\"<br/>\"+i+\"=\"+\"{<br/>\";

var o=info[i];

for(var j in o){

temp+=\"    \"+j+\"=\"\"+o[j]+\"\"<br/>\";

}

temp+=\"}\";

}

}

var p=document.getElementById(\"test\");

p.innerHTML=temp;

}


第4步,設計客戶端交互頁面與信息展示。這一步比較簡單,可以在頁面中插入一個<p>標籤,然後把輸出的信息插入到該元素中。同時為頁面設計一個交互按鈕,單擊該按鈕將觸發請求函數,並且向服務器端發出URL Service請求。響應完畢,在Script Tags字符串被傳回到客戶端之後,將調用回調函數對響應的數據進行處理和顯示。


<p></p>