第一次發文章,有點小緊張。
什麼是同源策略?
為什麼要有跨域限制?
為什麼script標籤能跨域?
這些想必不用說明了,我們切入正題:
再學習黃老師的【Vue 2.0開發企業級移動端音樂Web APP】時,因為課程的資料是通過Jsonp獲取的,當時聽到講解原理,不甚理解。於是點開jsonp包的原始碼研磨一番,原來實現原理是這樣的:
1. 客戶端需要在自己這邊有一個定義好的接收服務端資料的函式
2. 使用script標籤發起一個服務端地址的請求
3. 服務端響應,對應的函式執行,傳參完成
這是最簡單的jsonp實現,這裡需要寫一個函式,需要寫一個script標籤,好像一點都不高階~
接下來我們手寫一個jsonp,當然了,服務端怎麼知道你本地的準備函式是什麼,需要客戶端告訴它,而且服務端會根據客戶端提供的其他引數動態生成資料給到我們。所以我們主要是實現以下幾個點:
1. 呼叫時動態建立script標籤
2. 註冊一個全域性的方法等待被執行
3. 等待函式一定要在標籤發起請求之前準備好
4. 得到資料之後移除建立的script標籤
5. 函式名不能重名,可能會同時發起多個請求
從上所述,在這裡我們的方法需要以下幾個引數:
. url: 後端地址
. prefix: 執行函式名的字首,字尾使用自增來確保唯一性
. param: 與後端協商好的發起jsonp請求時的欄位
. timeout: 請求超時時間
. data: 服務端需要的其他引數
jsonp實現跨域請求也有自己的侷限,如只能發起get請求,原有的jsonp包url後面的引數需要自己新增上,使用的是回撥函式的形式處理資料,使用的是ES5的寫法,有了以上幾個問題,我們可以簡單的重構下:
// 其他引數在opts內
function jsonp(url, opts) {
// 實現Promise化
return new Promise((resolve, reject) => {
// 自增值初始化
let count = 0;
//設定預設引數
const {
prefix = '__jp',
param = 'callback',
timeout = 60000,
data = {}
} = opts;
let name = prefix + count++;
let timer;
//清除script標籤以及註冊的全域性函式以及超時定時器
function cleanup() { // 清除函式
if (script.parentNode) {
script.parentNode.removeChild(script);
window[name] = null;
if (timer) {
clearTimeout(timer);
}
}
}
if (timeout) { // 超時
timer = setTimeout(() => {
cleanup();
reject('timeout');
}, timeout);
}
// 註冊全域性函式,等待執行中...
window[name] = res => {
// 只要這個函式一執行,就表示請求成功,可以使用清除函式了
if (window[name]) {
cleanup();
}
// 將請求到的資料扔給then
resolve(res);
}
// 以下將data物件格式的引數拼接到url的後面
let str = '';
for (const key in data) {
const value = data[key] !== undefined ? data[key] : '';
str += `&${key}=${encodeURIComponent(value)}`;
}
url = url + (url.indexOf('?') > 0 ? '' : '?') + str.substr(1);
最後加上與服務端協商的jsonp請求欄位
url = `${url}&${param}=${name}`;
const script = document.createElement('script');
script.src = url;
// 以下這條執行且成功後,全域性等待函式就會被執行
document.head.appendChild(script);
})
}
複製程式碼
# 大功告成,我們請求一個QQ音樂的輪播圖資料試下:
const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg';
const opts = {
data: {
g_tk: 1928093487,
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0,
format: 'jsonp',
platform: 'h5',
uin: 0,
needNewCode: 1
},
// QQ音樂介面Jsonp欄位
param: 'jsonpCallback'
}
jsonp(url, opts)
.then(res => {
console.log(res);
})
.catch(ex => {
console.log(ex);
})
複製程式碼
資料請求成功!