跨域的實現方式有多種,除了 上篇文章 提到的CORS外,常見的還有JSONP、HTML五、Flash、iframe、xhr2等。javascript
這篇文章對JSONP的跨域原理進行了探索,並將個人心得記錄在這裏和你們分享。css
咱們知道,使用 XMLHTTPRequest 對象發送HTTP請求時,會遇到 同源策略 問題,域不一樣請求會被瀏覽器攔截。html
那麼是否有方法能繞過 XMLHTTPRequest 對象進行HTTP跨域請求呢?前端
換句話說,不使用 XMLHTTPRequest 對象是否能夠發送跨域HTTP請求呢?java
細心的你可能會發現,像諸如:jquery
<script type="text/javascript" src="http://www.a.com/scripts/1.js"></script>ajax
<img src="http://www.b.com/images/1.jpg" />json
<link rel="stylesheet" href="http://www.c.com/assets/css/1.css" />跨域
這種標籤是不會遇到"跨域"問題的,嚴格意義上講,這不是跨域,跨域是指在腳本代碼中向非同源域發送HTTP請求,這只是跨站資源請求。瀏覽器
那麼,咱們是否能夠利用跨站資源請求這一方式來實現跨域HTTP請求呢?
以<script></script>標籤爲例進行探索,先看一段代碼:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>jsonp demo</title> <!-- JavaScript片段1 --> <script type="text/javascript"> function handler(data) { alert(data); // our code here... } </script> <!-- JavaScript片段2 --> <script type="text/javascript"> handler('success'); </script> </head> <body> A JSONP demo. </body> </html>
這段代碼中,有2個JavaScript片段,第1個片段中定義了一個處理函數handler(),這個處理函數比較簡單,沒有對數據作任何處理,只是把它alert出來;第2個片段調用了它,運行這個頁面瀏覽器會彈出"success"。
咱們假設第2個JavaScript片段存儲在別的地方,而後咱們使用<script src="" />的方式把它引入進來,像這樣:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>jsonp demo</title> <!-- JavaScript片段1 --> <script type="text/javascript"> function handler(data) { alert(data); // our code here... } </script> <!-- JavaScript片段2 --> <script type="text/javascript" src="http://service.a.com/script/1.js"></script> </head> <body> A JSONP demo. </body> </html>
service.a.com/script/1.js:
handler('success');
這種方法和把JavaScript代碼直接寫在頁面是等效的,可是,咱們由此能夠聯想到什麼?
咱們是否能夠事先在本頁面定義處理程序,服務端返回JS腳本,腳本的內容就是對處理程序的回調,服務返回的數據經過參數的形式傳回:
handler('服務返回的數據');
而後經過動態向當前頁面head節點添加<script src="服務地址"></script>節點的方式來「僞造」HTTP請求?
因而,能夠編寫這樣一個簡單的測試用例:
先寫服務端,很是簡單的一個服務,返回字符串"Hello World",通常處理程序Service.ashx:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace JSONPDemo.Service { /// <summary> /// Service2 的摘要說明 /// </summary> public class Service2 : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Write("handler('Hello World');"); } public bool IsReusable { get { return false; } } } }
再寫客戶端,一個簡單的靜態Web頁,index.html:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script type="text/javascript"> // 跨域發送HTTP請求,從服務端獲取字符串"Hello World" function getHello() { var script = document.createElement('script'); script.setAttribute('src', 'http://localhost:8546/Service.ashx'); document.querySelector("head").appendChild(script); } // 處理函數 function handler(data) { alert(data); // our code here... } </script> </head> <body> <input type="button" value="發送跨域HTTP請求,獲取Hello World" onclick="getHello()" /> </body> </html>
測試成功!
在這個測試例子中,咱們使用通常處理程序編寫了一個簡單的返回Hello World的服務,而後使用動態建立<script></script>節點的方式實現了跨域HTTP請求。
因爲<script>、<img>標籤資源請求是異步的,因此咱們就實現了一個跨域的異步HTTP請求。
可是這麼作是不夠的,一個頁面可能會有多個HTTP請求,而上面這個示例的處理程序只有一個——handler。
不一樣的請求應該由不一樣的處理程序來處理,對上面的代碼稍作修改,只須要給<script>標籤的src屬性中的URL添加一個參數來指定回調函數的名稱就能夠了:
服務端:
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; // 前端指定的回調函數名稱 var callbackFuncName = context.Request.QueryString["callback"]; var responseData = "Hello World"; // 回調腳本,形如:handler('responseData'); var scriptContent = string.Format("{0}('{1}');", callbackFuncName, responseData); context.Response.Write(scriptContent); }
Web客戶端:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>jsonp demo</title> <script type="text/javascript"> // 跨域發送HTTP請求,從服務端獲取字符串"Hello World" function getHello() { var script = document.createElement('script'); script.setAttribute('src', 'http://localhost:8546/Service.ashx?callback=handler');//callback指定回調函數名稱 document.querySelector("head").appendChild(script); } // 處理函數 function handler(data) { alert(data); // our code here... } </script> </head> <body> <input type="button" value="發送跨域HTTP請求,獲取Hello World" onclick="getHello()" /> </body> </html>
在上面的章節咱們探及了JSONP跨域的原理。幸運的是,咱們並不須要每次都寫這麼多代碼來完成一次跨域請求。
jQuery的ajax方法對JSONP式跨域請求進行了封裝,若是使用jQuery進行JSONP式跨域請求,代碼將會變得很是簡單:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>jQuery jsonp demo</title> <script src="jquery-1.11.0.min.js"></script> <script type="text/javascript"> $.ajax({ type: "get", url: "http://localhost:8546/Service.ashx", dataType: "jsonp",//必定要指明爲jsonp success: function (data) { alert(data); }, error: function () { alert('fail'); } }); </script> </head> <body> 使用jQuery一切將會變得很是簡單。 </body> </html>
只須要將dataType設置爲"jsonp"就能夠進行跨域請求了,一切就像發送非跨域請求那樣簡單。
jQuery爲咱們封裝好了回調函數,通常狀況下不須要咱們單獨去寫,若是你不想在success中處理,想單獨寫處理函數,那麼能夠經過設置這2個參數來實現:
必需要強調的是:
1.JSONP雖然看起來很像通常的ajax請求,但其原理不一樣,JSONP是對文章第一小節原理的封裝,是經過<script>標籤的動態加載來實現的跨域請求,而通常的ajax請求是經過XMLHttpRequest對象進行;
2.JSONP不是一種標準協議,其安全性和穩定性都不如 W3C 推薦的 CORS;
3.JSONP不支持POST請求,即便把請求類型設置爲post,其本質上仍然是一個get請求。
JavaScript中的跨域解決方案彙總: