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