前言
說到AJAX就會不可避免的面臨兩個問題,第一個是AJAX以何種格式來交換數據?第二個是跨域的需求如何解決?這兩個問題目前都有不同的解決方案,比如數據可以用自定義字符串或者用XML來描述,跨域可以通過服務器端代理來解決。 但到目前爲止最被推崇或者說首選的方案還是 用JSON來傳數據,靠JSONP來跨域。而這就是本文將要講述的內容。 JSON和JSONP雖然只有一個字母的差別,但其實他們根本不是一回事兒:JSON是一種數據交換格式,具體可以參見:json。而JSONP是一種依靠開發人員的聰明才智創造出的一種非官方跨域數據交互協議。而本文主要講述的是JSONP。
1. 什麼是跨域問題?
瀏覽器對ajax請求的限制,不允許跨域請求資源。
例:
http://www.a.com
→ http://www.b.com
是跨域
http://www.a.com
→ http://www.a.com:8080
是跨域
http://a.a.com
→ http://b.a.com
是跨域
http://www.a.com
→ http://www.a.com/api
不是跨域
由上可知,不同的域名或不同的端口都是跨域請求。
2. 跨域問題演示
- 遠程服務器
manage.taotao.com
根目錄下有個json.jsp
文件代碼如下:
- 本地服務器
www.taotao.com
下有個test-json.htm
頁面代碼如下:
- 調用本地服務器的
test-json.htm
頁面:
發現:
1. alert($)
可以正常彈出
2. alert(data.abc)
不能夠正常的彈出,出現跨域問題
**結論:**script標籤的src可以跨域請求資源,但是ajax請求不可以跨域請求。
3. 使用JSONP解決跨域問題
3.1 直接通過‹script›標籤獲取json數據
- 修改本地
www.taotao.com
中的test-json.htm
頁面代碼如下:
- 調用本地服務器的
test-json.htm
頁面:
發現: 請求資源可以正常請求,但是,報js解析出錯。
原因: Script標籤加載到資源後,會將資源當做是js腳本解析,但是我們返回的是json數據,所以導致解析失敗。 解決: 必須返回js腳本。
3.2 返回js包裝後的json
遠程服務器remoteserver.com
根目錄下有個remote.js
文件代碼如下:
alert('我是遠程文件');
本地服務器localserver.com
下有個jsonp.html
頁面代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="http://remoteserver.com/remote.js"></script> </head> <body> </body> </html>
測試:頁面將會彈出一個提示窗體,顯示跨域調用成功。
3.3 傳入函數進行調用
現在我們在jsonp.html
頁面定義一個函數,然後在遠程remote.js
中傳入數據進行調用。
jsonp.html頁面代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> var localHandler = function(data){ alert('我是本地函數,可以被跨域的remote.js文件調用,遠程js帶來的數據是:' + data.result); }; </script> <script type="text/javascript" src="http://remoteserver.com/remote.js"></script> </head> <body> </body> </html>
remote.js文件代碼如下:
localHandler({"result":"我是遠程js帶來的數據"});
運行之後查看結果,頁面成功彈出提示窗口,顯示本地函數被跨域的遠程js調用成功,並且還接收到了遠程js帶來的數據。很欣喜,跨域遠程獲取數據的目的基本實現了,但是又一個問題出現了,我怎麼讓遠程js知道它應該調用的本地函數叫什麼名字呢?畢竟是jsonp的服務者都要面對很多服務對象,而這些服務對象各自的本地函數都不相同啊?我們接着往下看。
3.4 動態的函數調用
聰明的開發者很容易想到,只要服務端提供的js腳本是動態生成的就行了唄,這樣調用者可以傳一個參數過去告訴服務端“我想要一段調用XXX函數的js代碼,請你返回給我”,於是服務器就可以按照客戶端的需求來生成js腳本並響應了。
修改jsonp.html
頁面的代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> // 得到航班信息查詢結果後的回調函數 var flightHandler = function(data){ alert('你查詢的航班結果是:票價 ' + data.price + ' 元,' + '餘票 ' + data.tickets + ' 張。'); }; // 提供jsonp服務的url地址(不管是什麼類型的地址,最終生成的返回值都是一段javascript代碼) var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler"; // 創建script標籤,設置其屬性 var script = document.createElement('script'); script.setAttribute('src', url); // 把script標籤加入head,此時調用開始 document.getElementsByTagName('head')[0].appendChild(script); </script> </head> <body> </body> </html>
這次的代碼變化比較大,不再直接把遠程js文件寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分,本例中的重點也就在於如何完成jsonp調用的全過程。
我們看到調用的url中傳遞了一個code參數,告訴服務器我要查的是CA1998次航班的信息,而callback參數則告訴服務器,我的本地回調函數叫做flightHandler,所以請把查詢結果傳入這個函數中進行調用。
OK,服務器很聰明,這個叫做flightResult.aspx的頁面生成了一段這樣的代碼提供給jsonp.html(服務端的實現這裏就不演示了,與你選用的語言無關,說到底就是拼接字符串):
flightHandler({ "code": "CA1998", "price": 1780, "tickets": 5 });
我們看到,傳遞給flightHandler
函數的是一個json,它描述了航班的基本信息。運行一下頁面,成功彈出提示窗口,jsonp的執行全過程順利完成!那麼調用的過程還能更簡單點嗎?接着往下看。
3.5 通過jquery實現jsonp調用
修改jsonp.html
頁面的代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>Untitled Page</title> <script type="text/javascript" src=jquery.min.js"></script> <script type="text/javascript"> jQuery(document).ready(function(){ $.ajax({ type: "get", async: false, url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998", dataType: "jsonp", jsonp: "callback",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認爲:callback) jsonpCallback:"flightHandler",//自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動爲你處理數據 success: function(json){ alert('您查詢到航班信息:票價: ' + json.price + ' 元,餘票: ' + json.tickets + ' 張。'); }, error: function(){ alert('fail'); } }); }); </script> </head> <body> </body> </html>
是不是有點奇怪?爲什麼我這次沒有寫flightHandler這個函數呢?而且竟然也運行成功了!哈哈,這就是jQuery的功勞了,jquery在處理jsonp類型的ajax時(還是忍不住吐槽,雖然jquery也把jsonp歸入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調函數並把數據取出來供success屬性方法來調用,是不是很爽呀?
4. 總結
最後,我們可以總結一下jsonp吧!它是爲了便於客戶端使用數據,逐漸形成了一種非正式傳輸協議,該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然後服務端返回數據時會將這個callback參數作爲函數名來包裹住JSON數據,這樣客戶端就可以隨意定製自己的函數來自動處理返回數據了。
Jsonp的原理:
- jsonp通過script標籤的src可以跨域請求的特性,加載資源
- 將加載的資源(通過一個方法名將數據進行包裹)當做是js腳本解析
- 定義一個回調函數,獲取傳入的數據