第一次发文章,有点小紧张。
什么是同源策略?
为什么要有跨域限制?
为什么script
标签能跨域?
这些基本的概念想必不用过多说明了,我们切入正题:
在开发项目中如果有用到jsonp
,一直使用的是一个jsonp
包。使用过程中参数需要格式化,也未曾进行Promise
化,需要自己封装。于是想想能不能在包内就把这些事给办了,以后调用就省心很多,于是点开jsonp
包的源码研读一番,这里简单说明下jsonp
的实现原理,简单来说就是这样的:
1. 客户端需要在自己这边有一个定义好的接收服务端数据的函数
2. 使用script标签发起一个服务端地址的请求
3. 服务端响应,对应的函数执行,传参完成
这是最简单的jsonp
实现,这里需要写一个函数,需要写一个script
标签,好像一点都不cool
~
接下来我们手写一个自己的jsonp
,当然了,服务端怎么知道你本地的准备函数是什么,需要客户端告诉它,而且服务端会根据客户端提供的其他参数动态生成数据给到我们。所以我们主要是实现以下几个点:
1. 调用时动态创建script标签
2. 注册一个全局的方法等待被执行
3. 等待函数一定要在标签发起请求之前准备好
4. 得到数据之后移除创建的script标签
5. 函数名不能重名,可能会同时发起多个请求
从上所述,在这里我们的方法需要以下几个参数:
. url: 后端地址
. prefix: 执行函数名的前缀,后缀使用自增来确保唯一性
. param: 与后端协商好的发起jsonp请求时的字段
. timeout: 请求超时时间
. data: 服务端需要的其他参数
jsonp
实现跨域请求也有自己的局限,如只能发起get
请求。我们这里再原有的包上进行一定功能拓展,使用es6
重构并将它Promise
化,重构之后的代码jsonp
如下:
// 其他参数在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);
})
数据请求成功!