今天在開發聯調的過程中,需要跨域的獲取數據,因為使用的jquery,當然使用dataType:'jsonp'就能夠很easy的解決了。
但是因為當時後端沒有支持jsonp來訪問,後來他在實現這個功能的時候問了我一句,jsonp形式返回的格式是怎麼樣子的?我一直以來只知道怎麼使用,迷迷糊糊的卻沒有答上來。。。
雖然後來解決了,但是對於喜歡解決問題的我,心裡卻一直耿耿於懷,必須得把這個研究透徹了,於是我開始翻閱資料,看到後面真有種豁然開朗的感覺,於是打算做個筆記與大家分享。
JSON和JSONP的區別
JSON和JSONP雖然只有一個字母的差別,但其實他們根本不是一回事兒:JSON是一種數據交換格式,而JSONP是一種跨域數據交互的協議,使用JSONP方法獲取到的仍然是json格式的數據。
說白了,用JSON來傳數據,靠JSONP來跨域。
JSONP詳細闡述
我們都知道,一個頁面的ajax只能獲取和此頁面同域的數據。,所以當我們需要跨域獲取數據的時候就需要使用到JSONP方法來獲取了。
如下圖所示,就是使用json格式獲取跨域數據返回的錯誤提示:

那麼該如何解決呢?使用框架的前端童鞋們可能都有自己相應的辦法,比如jquery就是把dataType設為jsonp就能解決了,但是我們在使用的時候有沒有想過,為什麼這樣就能解決呢?中心思想又是什麼呢?
下面就開始為大家詳細闡述,首要思想就是利用scirpt標簽來引入跨域的數據。我們從最開始慢慢來深入jsonp的過程。
引導步驟1
編寫b.com/b.js內容:
復制代碼 代碼如下:alert(‘hello');
然後編寫a.com/a.html內容:
復制代碼 代碼如下:<script type='text/javascript' src='http://b.com/b.js'>
運行a.html,結果很明顯,肯定會彈出hello。
引導步驟2
修改b.com/b.js文件內容:
復制代碼 代碼如下:myFunction('hello');
然後修改a.com/a.html內容:
<script type='text/javascript' src='http://b.com/b.js'>
<script>
function myFunction(str)
{ //定義處理數據的函數
alert(str + ' world');
}
</script>
運行a.html 結果是彈出‘hello world'。這個應該也毫無疑問。
引導步驟3
讓我們再看一下上面的步驟2,b.js中的‘hello'就是b.com域名下的數據了,而能夠在a.com/a.html中執行顯示出來,這不就已經實現了跨域請求數據了嗎?
另外,因為script標簽中的src 不一定要指向js文件,而可以指向任何地址。
所以,我們把上面步驟2中a.html的內容:<script type='text/javascript' src='http://b.com/b.js'>,我們把其中的b.js改成b.html或者b.json等等都是可以的,執行都能正常返回。
引導步驟4
上面的數據都是靜態的,是在文件內寫死的,所以並不能滿足我們的需求了吧。。。因為我們ajax請求數據是實時變化的,所以我們要把數據變成動態的了。
我們可以讓script表器去調用一個動態的頁面(接口),去實現獲取動態數據,這裡就想到了回調函數.
編輯a.com/a.html頁面內容:
<script type='text/javascript' src='http://b.com/b.aspx?callback=myFunction'>
<script>
function myFunction(str){ //定義處理數據的函數
alert(str + ' world');
}
</script>
我們在src引用地址中加了?callback=myFunction,意思是把顯示數據的函數也動態的傳入了。
使用jsonp方法獲取數據,還有一個要點就是後端接口也要支持jsonp才行,比如下面一段代碼就是讓返回的數據變成jsonp的格式,請繼續看:(此處使用.net語言作為例子)
protected void page_load(object sender, EventArgs e){
if(this.IsPostBack == false){
string callback = '';
if(Request["callback"] != null){
callback = request["callback"];
string data = "hello";
Response.Write(callback+"("+ data + ")"); //接口頁面返回的數據格式“函數(參數)”的格式。
}
}
}
代碼的意思很簡單,就是獲取調用函數的參數。如果這裡調用b.aspx?callback=myFunction的話,則會返回myFunction('hello'),如果後端代碼給data賦值一個變量,這裡的‘hello'則變成了動態的數據了。
引導步驟5
再看上面的步驟,雖然獲取的數據是動態的了,但在頁面上引入一個script標簽,卻只能執行一次,獲取一次,顯然還是不能滿足需求的。所以我們在需要的時候,就得動態的添加一次這樣的script標簽。
所以我們在這裡需要封裝一個函數:
function addScript(src){
var script = document.createElement('script');
script.setAttribute('type','text/javascript');
script.src= src;
document.body.appendChild(script);
}
需要調用的時候,就去執行:
addScript('b.com/b.aspx?callback=myFunction');
function myFunction(data){//定義處理數據的函數
alert(data);
}
ok,上面的過程就是jsonp的原理,我們不必去記住那些令人糾結不清的定義,只要看一遍這個過程,我相信就能明白其中的精髓了吧。
jquery實現跨域
jquery跨域方法
$.ajax({
url: 'b.com/b.json', //不同的域
type: 'GET', // jsonp模式只有GET是合法的
dataType: 'jsonp', // 數據類型
jsonp: 'callback', // 指定回調函數名,與服務器端接收的一致,並回傳回來
success: function(data) {
console.log(data);
}
})
使用jquery非常方便,那麼它是怎麼實現這個轉化的呢?下面我們來看看這部分的jquery源碼。
jq實現jsonp源碼分析
我貼出網上給的jquery實現jsonp部分的源碼分析:
if (s.dataType == "jsonp") { // 構建jsonp請求字符集串。jsonp是跨域請求,要加上callback=?後面將會加函數名
if (type == "GET") { //使get的url包含 callback=?後面將 會進行加函數名
if (!s.url.match(jsre)) s.url += (s.url.match(/?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
} // 構建新的s.data,使其包含 callback=function name
else if (!s.data || !s.data.match(jsre)) s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
s.dataType = "json";
}
//判斷是否為jsonp,如果是 ,進行處理。
if (s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre))) {
jsonp = "jsonp" + jsc ++; //為請 求字符集串的callback=加上生成回調函數名
if (s.data) s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
s.url = s.url.replace(jsre, "=" + jsonp + "$1"); // 我們需要保證jsonp 類 型響應能正確地執行
//jsonp的類型必須為script。這樣才能執行服 務器返回的
//代碼。這裡就是調用這個回調函數。
s.dataType = "script";
//window下注冊一個jsonp回調函數 有,讓ajax請求返回的代碼調用執行它,
window[jsonp] = function(tmp) {
data = tmp;
success();
complete(); // 垃圾回收,釋放聯變量,刪除jsonp的對象,除去head中加的script元素
window[jsonp] = undefined;
try {
delete window[jsonp];
} catch (e) {}
if (head) head.removeChild(script);
};
}
if (s.data && type == "GET") { // data有效,追加到get類型的url上去
s.url += (s.url.match(/?/) ? "&" : "?") + s.data; // 防止IE會重復發送get和post data
s.data = null;
}
if (s.dataType == "script" && type == "GET" && parts && (parts[1] && parts[1] != location.protocol || parts[2] != location.host)) { // 在head中加上<script src=""></script>
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = s.url;
if (s.scriptCharset) script.charset = s.scriptCharset;
if (!jsonp) { //如果datatype不是jsonp,但是url卻是跨域 的。采用scriptr的onload或onreadystatechange事件來觸發回 調函數。
var done = false; // 對所有浏覽器都加上處理器
script.onload = script.onreadystatechange = function() {
if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
done = true;
success();
complete();
head.removeChild(script);
}
};
}
head.appendChild(script); // 已經使用 了script 元素注射來處理所有的事情
return undefined;
}
上面的代碼稍顯復雜,但是我們挑揀重要的看就好了。
我們來分析一下這個過程,其實這個過程也就是上面我提出問題的答案了:
這裡執行代碼之後,其實就是判斷是否配置了dataType: 'jsonp',如果是jsonp協議,則要在url上加callback=jQueryxxx(函數名),jquery會把url轉化為:http://b.com/b.json?callback=jQueryxxx,然後再在html中插入,加載完b.json這個文件後,就會執行jQueryxxx這個回調函數,而且此時這個函數裡面已經存在了動態數據(json格式數據),所以在頁面上執行的時候就能夠隨心所欲的處理數據了,但是也別忘了後端也要支持jsonp格式才行。所以這樣就達到了跨域獲取數據的功能。
原生js封裝jsonp
function jsonp(config) {
var options = config || {}; // 需要配置url, success, time, fail四個屬性
var callbackName = ('jsonp_' + Math.random()).replace(".", "");
var oHead = document.getElementsByTagName('head')[0];
var oScript = document.createElement('script');
oHead.appendChild(oScript);
window[callbackName] = function(json) { //創建jsonp回調函數
oHead.removeChild(oScript);
clearTimeout(oScript.timer);
window[callbackName] = null;
options.success && options.success(json); //先刪除script標簽,實際上執行的是success函數
};
oScript.src = options.url + '?' + callbackName; //發送請求
if (options.time) { //設置超時處理
oScript.timer = setTimeout(function () {
window[callbackName] = null;
oHead.removeChild(oScript);
options.fail && options.fail({ message: "超時" });
}, options.time);
}
};
這是我自己寫的一個原生js實現jsonp獲取跨域數據的方法。
我們只需要調用jsonp函數就能夠跨域獲取數據了。比如:
jsonp({
url: '/b.com/b.json',
success: function(d){
//數據處理
},
time: 5000,
fail: function(){
//錯誤處理
}
})
小結
再說幾點注意的地方:
使用jsonp方法時,在控制台的network-JS中才能找到調用的接口,不再是XHR類了。由於頁面渲染的時候script只執行一次,而且動態數據需要多次調用,所以在插入使用之後需要刪除,並且要初始化回調函數。原生js實現時,最好加一個請求超時的功能,方便調試。
總之jsonp就是一種獲取跨域json數據的方法。