JSONP實作原理
jsonp,其實就是單純為了實作跨域請求而創造的一個欺騙(trick)。
雖然,因為同源政策的影響,不能通過XMLHttpRequest請求不同域上的資料(Cross -origin reads)。但是,在頁面上引入不同域上的js腳本檔案卻是可以的(Cross -origin embedding)。是以,在js檔案載入完畢之後,觸發回調,可以将需要的data作為參數傳入
注意,實作方式(需前後端配合)
優點
相容性好(相容低版本IE)
缺點
JSONP隻支援GET請求,XMLHttpRequest相對于JSONP有着更好的錯誤處理機制。
實作
1.服務端采用的是Node,服務端處理請求方法如下
router.get('/', function(req, res, next) {
console.log('收到用戶端的請求:', req.query);
// 傳回到用戶端的資料
let data = JSON.stringify({
'status':200,
'result':{
'name':'柳成蔭',
'site':'123456'
}
});
// 擷取方法名稱 - 這是用戶端傳過來的方法名參數
// 因為這個方法名必須是用戶端有的,必須要用戶端告訴服務端是哪個方法
let methodName = req.query.callback;
let methodStr = methodName + '(' + data + ')';
// 快速結束沒有任何資料的響應,用戶端會執行這個方法,進而擷取到服務端傳回的資料
res.end(methodStr)
});
2.封裝JSONP
(function (w) {
/**
* jsonp的實作
* @param {Object}option
*/
function jsonp(option) {
// 把success函數挂載在全局的getDate函數上
w.getData = option.success;
// 處理url,拼接參數 - 回調方法是getData
option.url = option.url + '?callback=getData';
// 建立script标簽,并插入body
let scriptEle = document.createElement('script');
scriptEle.src = option.url;
document.body.appendChild(scriptEle);
}
// 全局挂載一個jsonp函數
w.jsonp = jsonp;
})(window);
/**
* 把對象轉換成拼接字元串
* 把形如
data:{
"sex":"男",
"name":"九月"
}
轉換成sex=男&name=九月
* @param paramObj 對象參數
* @param words
* @returns {string} 字元串
*/
function getStrWithObject(paramObj,words){
let resArr = [];
// 1.轉換對象
for(let key in paramObj){
let str = key + '=' + paramObj[key];
resArr.push(str);
}
resArr.push(words);
// 3.數組轉換成字元串
return resArr.join("&");
}
看似代碼沒有任何問題。但是,我們測試一下,多次調用jsonp方法。
jsonp({
url:'http://localhost:3000/',
data:{
"sex":"男",
"name":"九月"
},
success:function (data) {
console.log(data);
alert(1);
}
});
jsonp({
url:'http://localhost:3000/',
data:{
"sex":"男",
"name":"九月"
},
success:function (data) {
console.log(data);
alert(2);
}
});
結果會怎麼樣?的确,還是會執行2次,但是每次執行的都是第二個jsonp方法。這是為什麼?
問題出現在這裡,多次調用,會發生函數覆寫。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLwYDN3UjNzEjM4EjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
解決方案就是讓每一次調用函數名不一緻,在JS裡有很多方法,比如通過随機數、時間戳之類的。這裡采用的是随機數,修改如下。
// 0.産生不同的函數名 - 解決了調用多次請求,造成覆寫的問題
// 如果方法名相同,調用多次,都會執行最後一個,出現覆寫現象
let callBackName = 'lcy' + Math.random().toString().substr(2)
+ Math.random().toString().substr(2);
// 把success函數挂載在全局的getDate函數上
w[callBackName] = option.success;
// 處理url,拼接參數
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
好,現在看似完美解決。然而,我們發現,每次調用jsonp方法,都會在body裡插入一個script标簽。我們并不希望在調用jsonp之後,添加這樣一個标簽,我們需要在調用完之後,将其移除。
怎麼做呢?我們在将success函數挂載在全局時,我們在success外層再套個函數。在這個函數裡調用success方法之後,即已經請求到資料之後,我們就去把這個script标簽給它移除就行了。修改如下
// 1.函數挂載在全局
w[callBackName] = function(data){
option.success(data);
// 删除script标簽
document.body.removeChild(scriptEle);
};
整個過程就是這樣,以下是完整代碼。
(function (w) {
/**
* jsonp的實作
* @param {Object}option
*/
function jsonp(option) {
// 0.産生不同的函數名 - 解決了調用多次請求,造成覆寫的問題
// 如果方法名相同,調用多次,都會執行最後一個,出現覆寫現象
let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
// 1.函數挂載在全局
w[callBackName] = function(data){
option.success(data);
// 删除script标簽
document.body.removeChild(scriptEle);
};
// 2.處理url
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
// 3.建立script标簽插入body
let scriptEle = document.createElement('script');
scriptEle.src = option.url;
document.body.appendChild(scriptEle);
}
w.jsonp = jsonp;
})(window);
/**
* 把對象轉換成拼接字元串
* @param paramObj 對象參數
* @returns {string} 字元串
*/
function getStrWithObject(paramObj,words){
let resArr = [];
// 1.轉換對象
for(let key in paramObj){
let str = key + '=' + paramObj[key];
resArr.push(str);
}
resArr.push(words);
// 3.數組轉換成字元串
return resArr.join("&");
}