1. 程式人生 > >Ajax 跨域- JSONP例項和原理解析

Ajax 跨域- JSONP例項和原理解析

解決Ajax 跨域問題 - JSONP原理解析
為什麼會有跨域問題? - 因為有同源策略
同源策略是瀏覽器的一種安全策略,所謂同源指的是 請求URL地址中的 協議, 域名 和 埠 都相同,只要其中之一不相同就是跨域

同源策略主要為了保證瀏覽器的安全性

在同源策略下,瀏覽器 不允許 Ajax跨域獲取伺服器資料

    http://www.example.com/detail.html
    跨域請求:
        http://api.example.com/detail.html 域名不同
        http://www.example.com:8080/detail.html 埠不同
        http://api.example.com:8080/detail.html 域名、埠不同
        https://api.example.com/detail.html  協議、域名不同
        https://www.example.com:8080/detail.html 埠、協議不同

使用 jquery ajax-JSONP方式呼叫 解決跨域
雖然 jsonp 的實現跟 ajax 沒有半毛錢關係,jsonp是通過 script的src實現的(具體看後面的解析),但是最終目的都是向伺服器請求資料然後回撥,而且為了方便,所以jquery把 jsonp 也封裝在了 $.ajax 方法中,呼叫方式與 ajax 呼叫方式略有區別,案例如下。

下面直接看示例

測試jq_jsonp:

目標請求地址, http://www.jepson.com/jq_jsonp.php 

  <?php
        $cb = $_GET[ 'callback' ];    // 獲取回撥
        $arr = array( 'username' => 'zhangsan', 'password' => '123456' );
        echo $cb.'('. json_encode($arr) .');'; // 返回回撥函式呼叫

        // 就是 jQuery1111011117830135968942_1478857963357({"username":"zhangsan","password":"123456"});
    ?>


在 peter.com/jq_jsonp.html 裡面,通過 Jquery ajax 方法, JSONP 方式呼叫,進行請求

    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">
        $(function(){
            $("#btn").click(function(){
                $.ajax({
                    type:'get',
                    url:'http://www.jepson.com/jq_jsonp.php',
                    dataType:'jsonp',
                    jsonp: 'callback', // 回撥函式,後臺get接收的變數名,預設 就callback
                    // jsonpCallback: 'abc',    回撥函式名,預設是一長串,jquery自動生成的,在請求url中可以看到
                                            // 改不改無所謂,改短一點也只是 檢視請求url的時候清晰一點而已
                    success:function(data){
                        console.log(data.username,data.password);   // zhangsan 123456
                    },
                    error:function(data){
                        console.dir(data);
                        console.log('error');
                    }
                });
            });
        });
    </script>
    <body>
        <input type="button" value="點選" id="btn">
    </body>



可以看到,輸出了 ‘zhangsan 123456’。

注意:若後臺沒做獲取回撥,返回函式呼叫的工作,那麼就會出錯,進入 error方法。這是工作中常會遇到的錯誤,這裡要注意

解決方式 JSONP 的原理解析
JSONP的原理:利用 script標籤 的 src屬性 進行跨域請求,伺服器響應函式呼叫傳參。

這裡瞭解 JSONP的原理 並進行測試,需要不同域,我這裡是 apache 伺服器,自己配置了 兩個虛擬主機 做的。

兩個域 程式碼所在域 http://www.peter.com/, 請求域 http://www.jepson.com/

JSONP基本原理 - 靜態方法建立
我們通過下面靜態方法建立的測試來看看JSONP解決跨域問題的基本原理 
- 測試1:

目標請求地址,http:/www.jepson.com/1.php

 

    <?php
          echo "1;";
       ?>



在 peter.com/1.html 裡面,通過 script標籤進行請求

   

<script type="text/javascript" src="http://www.jepson.com/1.php"></script>


開啟F12,進入 network,找到 1.php的那個請求,選中 Response,發現結果是 1,跨域請求就這麼成功了! 沒錯原理就是這麼簡單。

但是有沒有發現,我們是無法獲得這個請求到的資料的,怎麼辦?

script標籤預設是同步載入的,那我們就 echo ‘var a = 1;’ ,獲取過來就是‘var a = 1;’, 那是不是就是可以認為聲明瞭一個變數並賦值了?

又因為是同步的,所以後面的 script語句是不是就可以用這個 a 了?我們來試一試 
- 測試2:

目標請求地址,http:/www.jepson.com/2.php,就一行

 

   <?php
        echo "var a = 1;";
     ?>


在 peter.com/2.html 裡面,通過 script標籤進行請求

 

  <!--  我是不是可以理解為這裡聲明瞭一個變數? var a = 1; -->
    <script type="text/javascript" src="http://www.jepson.com/2.php"></script>
    <script type="text/javascript">
        console.log( a );   // 1
    </script>



F12 開啟控制檯,沒有錯!你輸出 a 了, 輸出的值為 1,我們拿到了後臺傳輸的資料

但是,這種靜態的方式明顯有很多弊端,首先要放在程式碼的頂部,不然下面的沒法用到返回的資料

其次,這種靜態的方式傳參配置很不方便,於是乎我們一般採用 動態建立 script標籤的方式,新增到頭部,並配引數

JSONP基本原理 - 動態方法建立
動態建立 script標籤的方式,新增到頭部,並配引數,可以解決靜態建立的弊端。 
- 測試3:

目標請求地址,http:/www.jepson.com/3.php,就一行

 

  <?php
        echo "var a = 1;";
     ?>



在 peter.com/3.html 裡面,通過 script語句動態建立 script標籤進行請求

   

    <script type="text/javascript">
           var script = document.createElement('script');
           script.src = 'http://www.jepson.com/3.php';
           var head = document.getElementsByTagName('head')[0];
           head.appendChild(script);
           console.log( a );
       </script>



開啟 F12,令人震驚的事情發生了,** 居然報錯了,**Uncaught ReferenceError: a is not defined(…) ?? a變數沒有 定義?

我們開啟 network,找到 1.php這個請求,點開response,發現有 ‘var a = 1;’,請求成功了呀?這是什麼情況?

注意注意:原來動態建立 script 的方式傳送請求 是非同步的,雖然請求成功了,但是在使用變數時,請求還沒有完成,相當於還沒有定義變數,然後就報錯了。

那怎麼辦?這裡有一個小技巧, 我們可以 echo ‘foo( 123 )’; 這樣相當於請求完畢執行 foo(123),即呼叫我們 程式碼中foo函式,我們在程式碼中寫一個函式 function foo( data ) { console.log( data ); } 這就相當於進行了回撥,我們拿到了資料。下面是程式碼演示。

測試4:

目標請求地址,http:/www.jepson.com/4.php

 

  <?php
        echo 'foo(123)';
     ?>


在 peter.com/4.html 裡面,通過 script語句動態建立 script標籤進行請求 

   <script type="text/javascript">
        var script = document.createElement('script');
        script.src = 'http://www.jepson.com/4.php';
        var head = document.getElementsByTagName('head')[0];
        head.appendChild(script);

        function foo(data){
            console.log( data );    //foo(123) 對 foo 呼叫 輸出 123
        }
    </script>



F12 可以看到 輸出 123 了, 並且在network中檢視請求,response 可以看到是 foo(123)

那我們就可以用這種動態的方式 很輕鬆的 拿到後臺資料了,只不過前臺宣告的和 後臺 呼叫的 函式名 需要一樣才行,如上面的 foo,這樣就不太好了,每次改動,那都要對接一下。

所以我們可以把回撥函式名放在引數中傳輸。案例如下:

測試5:

目標請求地址,http:/www.jepson.com/5.php

 

   <?php
        $cb = $_GET[ 'callback' ];    // get 通過 callback鍵 得到 函式名
        $arr = array( 'username' => 'zhangsan', 'password' => '123456' );// 我們也可以傳複雜一點的資料
        echo $cb.'('. json_encode($arr) .');';// hello( json_encode($arr) )
    ?>


在 peter.com/5.html 裡面,通過 script語句動態建立 script標籤進行請求



**總結:**jsonp的本質:動態建立script標籤,然後通過它的src屬性發送跨域請求,然後伺服器端響應的資料格式為【函式呼叫】,所以在傳送請求之前必須先宣告一個函式,並且函式的名字與引數中傳遞的名字要一致。這裡宣告的函式是由伺服器響應的內容(實際就是一段函式呼叫js程式碼)來呼叫。JSONP是一個協議。

簡單封裝
上面就是 JSONP 的全部原理了,下面是 學習中實現的 jquery ajax JSONP 方式的 簡單封裝,感興趣的可以參考一下。

    function ajax( obj ){
        // 預設引數 由於 jsonp 原理是 在 url體 中傳遞,
        // 所以僅支援 get, 所以不對 type 進行配置
        var defaults = {
            url : '#',
            dataType : 'jsonp',
            jsonp : 'callback',
            data : {},
            success : function( data ) { console.log( data ) }
        }

        // 處理形參,傳遞函式的時候就覆蓋預設引數,不傳遞就使用預設的引數
        for ( var key in obj ) {
            defaults[ key ] = obj[ key ];
        }

        // 這裡是預設的回撥函式名稱,根據當前日期和隨機數生成
        var cbName = 'jQuery' + ('1.11.1' + Math.random()).replace(/\D/g,"") + '_' + (new Date().getTime());// 去掉所有的小點,相當於 jquery 後面加一串數字_再加上時間數字
        if( defaults.jsonpCallback ){
            cbName = defaults.jsonpCallback;
        }

        // 這裡就是回撥函式,呼叫方式:伺服器響應內容來呼叫
        // 向window物件中添加了一個方法,方法名稱是變數cbName的值
        window[ cbName ] = function( data ){
            defaults.success( data );//這裡success的data是實參
        }

        // 將所傳引數 data 進行處理,新增到 src url中
        var param = '';
        for( var attr in defaults.data ){
            param += attr + '=' + defaults.data[ attr ] + '&';
        }
        if( param ){  // 去掉最後的一個 &
            param = param.substring( 0, param.length-1 );
            param = '&' + param;
        }

        // 動態新增 script 標籤, 配置引數
        var script = document.createElement( 'script' );
        // defaults.jsonp 後臺 get 變數名,cbName 回撥函式名, param 變數
        script.src = defaults.url + '?' + defaults.jsonp + '=' + cbName + param; 
        var head = document.getElementsByTagName( 'head' )[ 0 ];
        head.appendChild( script );
    }


原文:https://blog.csdn.net/qq_16415157/article/details/53135537