淺析Ajax跨域原理及JQuery中的實現分析


  AJAX 的出現使得網頁可以通過在后台與服務器進行少量數據交換,實現網頁的局部刷新。但是出於安全的考慮,ajax不允許跨域通信。如果嘗試從不同的域請求數據,就會出現錯誤。如果能控制數據駐留的遠程服務器並且每個請求都前往同一域,就可以避免這些安全錯誤。但是,如果僅停留在自己的服務器上,Web 應用程序還有什么用處呢?如果需要從多個第三方服務器收集數據時,又該怎么辦?

 

 一、關於ajax跨域的思考

  1、Ajax為什么不能跨域?到底是卡在哪個環節了?(下面項目中具體說,這里先說下結論)。 Ajax其實就是向服務器發送一個GET或POST請求,然后取得服務器響應結果,返回客戶端。理論上這是沒有任何問題的,然而普通ajax跨域請求,在服務器端不會有任何問題,只是服務端響應數據返回給瀏覽器的時候,瀏覽器根據響應頭的Access-Control-Allow-Origin字段的值來判斷是否有權限獲取數據,一般情況下,服務器端如果沒有在這個字段做特殊處理的話,跨域是沒有權限訪問的,所以響應數據被瀏覽器給攔截了,所以在ajax回調函數里是獲取不到數據的(來自園友補充)。所以現在ajax跨域的問題可以轉化為數據怎么拿回客戶端的問題。

  2、既然不能直接訪問第三方站點,我們可以在服務器上面做代理,通過ajax向代理發送請求,代理獲得數據后在返回給客戶端,當然這是一種解決辦法,但是一次請求要從客戶端經過代理到第三方站點,然后再原路返回,響應速度是個問題。

  3、我們發現我們可以將一些js、css等文件放在第三方的服務器上面,如CDN等來加快網頁的打開速度,這樣是沒有任何問題的,也就是說web頁面可以加載放在任意站點的js、css、圖片等資源,不會受到"跨域"的影響。這個時候,我們會想到:既然我們可以調用第三方站點的js,那么如果我們將數據放到第三方站點的js中不就可以將數據帶到客戶端了嗎?

下面我們來做一個實驗,來驗證一下我們的猜想成不成立:

  打開Visual Studio,新建一個Web項目,這里用WebForm,然后我們在項目中添加一個名為remoteJs的js文件,寫入如下代碼:

function GetRemoteData() {
    return "remote data";
}

很簡單,就一個方法,返回一個字符串,下面我們來寫一個客戶端調用,既然是跨域,那就寫個html靜態頁面來測試吧,新建local.html,輸入以下代碼:

<!DOCTYPE html>
<html>
<head>
    <title>本地站點</title>
    <meta charset="UTF-8">
    <script type="text/javascript" src="http://localhost:4071/remoteJs.js"></script>
</head>
<script type="text/javascript">
    var data = GetRemoteData();
    alert(data);
</script>
<body>

</body>
</html>

讓我們的Web項目跑起來,然后打開local.html,可以看到彈出一個窗口,顯示信息remote data。這里證明我們的想法是正確的。接下來的問題是,我們如何根據需要發送請求和獲取請求的結果呢?下面我們來認識一下JSONP。

 

 二、JSONP

 1、什么是JSONP

  JSONP(JSON with Padding)是JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。其核心思想是利用JS標簽里面的跨域特性進行跨域數據訪問,在JS標簽里面存在的是一個跨域的URL,實際執行的時候通過這個URL獲得一段字符串,這段返回的字符串必須是一個合法的JS調用,通過EVAL這個字符串來完成對獲得的數據的處理。

  JSONP是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。

 

2、JSONP的實現

  下面我們通過一個例子來說明一下JSONP是如何實現ajax跨域請求的。這里我們模擬圖書館圖書的查詢,在剛剛我們建立的web項目里面添加一個名為SearchBook的一般處理程序,寫入如下代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace BookLibrary
{
    public class SearchBook : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            string callback = context.Request["callback"];
            context.Response.Write(callback + "({'BookName':'English','Pages':562})");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

暫時先不解釋,我們寫完客戶端看到效果后在詳細說明,然后修改剛剛的local.html,代碼如下:

<!DOCTYPE html>
<html>
<head>
    <title>跨域請求</title>
    <meta charset="UTF-8">
</head>
<body>
<input type="button" value="發送請求" onclick="GetAjaxData();" />
</body>
<script type="text/javascript">
    var GetData = function (data) {
      alert(data.BookName + " " + data.Pages);
   };
    function GetAjaxData(){
        var url = "http://localhost:4071/SearchBook.ashx?callback=GetData";
        var script = document.createElement('script');
        script.setAttribute('src', url);
        document.getElementsByTagName('head')[0].appendChild(script); 
    }
   
</script>
</html>
  

這個html頁面有一個按鈕,綁定方法GetAjaxData,當我們單擊發送請求時,就會向Web站點發送請求,獲取查詢的數據,我們讓Web站點跑起來,然后打開local.html,單擊按鈕,看到彈出如下信息:

我們成功的取得了web站點的數據,實現了跨域請求。下面我們來說一下他的實現原理:

var url = "http://localhost:4071/SearchBook.ashx?callback=GetData";

這一行代碼我們定義了請求的url,問號前面的是web站點一般處理程序SearchBook的地址,問號后面我們傳入了一個參數callback,值為GetData,也就是我們上面定義的方法名,及回調函數名稱。當然我們可以傳入更多的參數。

var script = document.createElement('script');
        script.setAttribute('src', url);
        document.getElementsByTagName('head')[0].appendChild(script); 

這三行代碼就是添加script節點,url指向第三方站點,執行結果如圖:

那么我們剛剛寫的一般處理程序返回的結果就不用說了吧,他就是返回一個字符串,內容為:

看到這里清楚了吧,就是第三方站點生成一個對回調函數的調用,傳入查詢結果,然后通過<script>加載到客戶端執行。看到這里是不是想到了什么?是不是和C#里面的委托有些共同點?整體流程如圖:

可以看到,整個過程,本地站點一直處於主動的地位,主動的發送請求,主動的加載遠程js.而第三方站點則處於被動的響應。

 

3、普通Ajax請求在哪個環節出錯了

下面,我們用JQuery的ajax來說明一下ajax請求到底是卡在哪個環節了,修改GetAjaxData方法如下:

           $.ajax({
                type: "get",
                async: false,
                url: "http://localhost:4071/SearchBook.ashx",
                dataType: "text",
                success: function (data) {
                    alert(data.BookName + " " + data.Pages);
                },
                error: function () {
                    alert('fail');
                }
            });

在SearchBook里面context.Response.Write(callback + "({'BookName':'English','Pages':562})");這行下端點,然后運行,會發現可以走到斷點,然后就出錯了。

 

4、用JQuery實現ajax跨域

其實JQuery里面也封裝了跨域的ajax方法,我們來看一下上面的方法用JQuery怎么寫:

<script type="text/javascript">
    function GetAjaxData() {
        $.ajax({
            type: "get",
            async: false,
            url: "http://localhost:4071/SearchBook.ashx",
            dataType: "jsonp",
            jsonp: "callback",//傳遞給請求處理程序或頁面的,標識jsonp回調函數名(一般為:callback)
            jsonpCallback: "GetData",//callback的function名稱
            success: function (data) {
                alert(data.BookName + " " + data.Pages);
            },
            error: function () {
                alert('fail');
            }
        });
    }
</script>

注意,JQuery寫法里面Url后面就不用再寫?來傳遞參數了,jsonp的值相當於?后面的值及參數名稱,jsonpCallback的值就是參數的value.success就是執行成功后調用的方法。

哎,不對啊,怎么沒有GetData方法了?JQuery到底是怎么實現的呢?下面我們來調試一下JQuery,來看一下里面是怎么實現的,調試js,當然還是要用Chrome,看圖:

這張圖中,我們看到有個對象s,在做url拼接操作,看到選中那行了吧,?后面拼的是s.jsonp,最后拼接的是callbackName.繼續向下走:

我們看到s.url的值,為拼接后的值,是不是和我們自己寫的js代碼里面的url一模一樣,繼續向下走:

我們看到JQuery又在剛剛的url后面添加下划線等號,然后又跟了一串數字,至於什么用,我也說不上來,繼續向下走:

看到了什么,success方法,哈哈,這是JQuery在變量參數,繼續走:

看到什么了?沒錯,這就是JQuery最終調用的方法,最后一行代碼,添加了script節點,和我自己寫js實現的原理一樣。繼續向下走,看看還有什么:

看到JQuery執行完后,又刪除了剛剛添加的script節點,還是JQuery想的周到啊~~ 

 下面我們來看一下,我們自己寫的js執行后的DOM結構:

看到了吧,script節點會隨着請求的次數一路飆升,不過並不會引起錯誤,刷新后就消失了。而JQuery執行后,DOM結構是不變的。

 

 三、總結

    1、ajax和jsonp這兩種技術在調用方式上“看起來”很像,目的也一樣,都是請求一個url,然后把服務器返回的數據進行處理,因此jquery和ext等框架都把jsonp作為ajax的一種形式進行了封裝;

  2、ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是通過HTTP來動態添加<script>標簽來調用服務器提供的js腳本。

  3、其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的數據的獲取。

  4、jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞數據,如果你願意,字符串都行,只不過這樣不利於用jsonp提供公開服務。

  5、jsonp整個過程中,本地站點一直處於主動的地位,主動的發送請求,主動的加載遠程js.而第三方站點則處於被動的響應。

 

 作者:雲霏霏

QQ交流群:243633526

 博客地址:http://www.cnblogs.com/yunfeifei/

 聲明:本博客原創文字只代表本人工作中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關系。非商業,未授權,貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

如果大家感覺我的博文對大家有幫助,請推薦支持一把,給我寫作的動力。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM