共计 6555 个字符,预计需要花费 17 分钟才能阅读完成。
什么是 JSONP
JSONP(JSON with Padding)是材料格局 JSON 的一种“应用模式”,能够让网页从别的网域获取材料。
因为浏览器同源策略,一般来说位于 server1.a.com 的网页无奈与 server2.a.com 的服务器沟通,而 HTML 的 <script> 元素是一个例外。利用 <script> 元素的这个凋谢策略,网页能够失去从其余起源动静产生的 JSON 材料,而这种应用模式就是所谓的 JSONP。用 JSONP 抓到的数据并不是 JSON,而是任意的 JavaScript,用 JavaScript 解释器执行而不是用 JSON 解析器解析。
JSONP 实现原理
jsonp 概念咱们理解了,那么如何实现呢?
咱们要获取一段 JSON 数据没有跨域问题,咱们能够通过 xhr GET 形式, 假如申请地址是以后域下 /apis/data?id=123
,以后域名是 example.com;
返回数据是
{
name=peng,
age=18
}
当咱们获取的数据不在同域的状况,比方上边的例子申请域名改成 example2.com,页面所应用的域名还是 example.com,应用 JSONP 的形式去跨域。
-
依据下面的原理简介,首先咱们在全局生命一个函数。
window.jsonp1 = function(data) {console.log(data) }
-
动静的往 head 标签中插入 script 标签
const head = document.getElementByTagName('head')[0] // 获取页面的 head 标签 const script = document.createElement('script') // 创立 script 标签 script.src = 'https://example2.com/apis/data?id=123&callback=jsonp1' // 给 script 标签赋值 src 地址 head.appendChild(script) // 最初插入 script 标签
-
最初须要 script 标签返回的内容是一个办法调用并传入参数是要返回的内容。
jsonp1({ name=peng, age=18 })
以上就对 JSONP 原理进行一个繁难的实现。
真正实现一个 JSONP 网络申请库
以上对 JSONP 原理和实现有了初步理解,如果咱们要在日常我的项目中应用,那就须要会封装一个残缺的 JSONP 网络申请库。
上面咱们对 jsonp-pro 网络申请库做一个源码剖析,从中理解并学习如何封装一个 JSONP 网络申请库。
先来看看申请的通用办法 method 库
// 查看类型的办法,用于对办法传入类型的限度
/**
* object check method
*
* @param {*} item variable will be check
* @param {string} type target type. Type value is 'String'|'Number'|'Boolean'|'Undefined'|'Null'|'Object'|'Function'|'Array'|'Date'|'RegExp'
* @return {boolean} true mean pass, false not pass
*/
function typeCheck(item, type) {
// 应用 Object.prototype.toString.call 办法,因为这个办法获取类型最全
const itemType = Object.prototype.toString.call(item);
// 拼接后果来做判断
let targetType = `[object ${type}]`;
if (itemType === targetType) {return true;} else {return false;}
}
// 获取随机数字型字符串,应用工夫戳 + 随机数拼接保障每次活的的字符串没有反复的
function randNum() {
// get random number
const oT = new Date().getTime().toString();
const num = Math.ceil(Math.random() * 10000000000);
const randStr = num.toString();
return oT + randStr;
}
export {typeCheck, randNum};
主文件,次要办法
import {typeCheck, randNum} from './methods';
// 传参的解释阐明,十分具体。这里不做过多解释
/**
* Param info
* @param {string} url url path to get data, It support url include data.
* @param {Object=} options all options look down
* @param {(Object | string)=} options.data this data is data to send. If is Object, Object will become a string eg. "?key1=value1&key2=value2" . If is string, String will add to at the end of url string.
* @param {Function=} options.success get data success callback function.
* @param {Function=} options.error get data error callback function.
* @param {Function=} options.loaded when data loaded callback function.
* @param {string=} options.callback custom callback key string , default 'callback'.
* @param {string=} options.callbackName callback value string.
* @param {boolean} options.noCallback no callback key and value. If true no these params. Default false have these params
* @param {string=} options.charset charset value set, Default not set any.
* @param {number=} options.timeoutTime timeout time set. Unit ms. Default 60000
* @param {Function=} options.timeout timeout callback. When timeout run this function.
* When you only set timeoutTime and not set timeout. Timeout methods is useless.
*/
export default function(url, options) {
// 获取 head 节点,并创立 scrpit 节点
const oHead = document.querySelector('head'),
script = document.createElement('script');
// 申明变量,并给局部值增加默认值
let timer, // 用于工夫定时器
dataStr = '', // 用于存传输的 query
callback = 'callback', // 和上边的参数一个含意
callbackName = `callback_${randNum()}`, // 和上边的参数一个含意
noCallback = false, // 和上边的参数一个含意
timeoutTime = 60000, // 和上边的参数一个含意
loaded, // 和上边的参数一个含意
success; // 和上边的参数一个含意
const endMethods = []; // 存储最初要执行回调函数队列
// 如果没有 url 参数抛出异样
if (!url) {throw new ReferenceError('No url ! Url is necessary !');
}
// 对 url 参数进行类型查看
if (!typeCheck(url, 'String')) {throw new TypeError('Url must be string !');
}
// 对所有参数进行解决的办法对象,命名与参数 key 放弃始终不便后续调用
const methods = {data() {
// data 参数解决办法
const data = options.data;
if (typeCheck(data, 'Object')) {
// 如果是对象类型将对象转换成 query 字符串并赋值给下面申明过的变量
for (let item in data) {dataStr += `${item}=${data[item]}&`;
}
} else if (typeCheck(data, 'String')) {
// 如果是字符串类型,间接赋值给上边变量
dataStr = data + '&';
} else {
// 其余状况抛出类型谬误
throw new TypeError('data must be object or string !');
}
},
success() {
// 对胜利参数办法进行解决
// 将胜利办法赋值给上边的变量
success = options.success;
// 进行类型查看,异样抛出谬误
if (!typeCheck(success, 'Function'))
throw new TypeError('param success must be function !');
},
error() {
// 对异样参数办法进行解决
// 进行类型查看,异样抛出谬误
if (!typeCheck(options.error, 'Function')) {throw new TypeError('param success must be function !');
}
// 类型查看通过,script 标签增加异样事件回调
script.addEventListener('error', options.error);
},
loaded() {
// 将加载实现办法进行解决
// 将加载实现办法赋值给上边变量
loaded = options.loaded;
// 进行类型查看,异样抛出谬误
if (!typeCheck(loaded, 'Function')) {throw new TypeError('param loaded must be function !');
}
},
callback() {
// 将 callback 参数进行解决
callback = options.callback;
// 进行类型查看,异样抛出谬误
if (!typeCheck(callback, 'String')) {throw new TypeError('param callback must be string !');
}
},
callbackName() {
// 将 callbackName 参数进行解决
callbackName = options.callbackName;
// 进行类型查看,异样抛出谬误
if (!typeCheck(callbackName, 'String')) {throw new TypeError('param callbackName must be string !');
}
},
noCallback() {
// 将 noCallback 参数进行解决
noCallback = options.noCallback;
// 进行类型查看,异样抛出谬误
if (!typeCheck(noCallback, 'Boolean')) {throw new TypeError('param noCallback must be boolean !');
}
},
charset() {
// 将 charse 参数进行解决
const charset = options.charset;
if (typeCheck(charset, 'String')) {
// 设置 script 标签 charset,浏览器个别默认是 UTF8,如果有非凡的须要手动设置
script.charset = charset;
} else {
// 进行类型查看,异样抛出谬误
throw new TypeError('param charset must be string !');
}
},
timeoutTime() {
// 将 timeoutTime 参数进行解决
timeoutTime = options.timeoutTime;
// 进行类型查看,异样抛出谬误
if (!typeCheck(timeoutTime, 'Number')) {throw new TypeError('param timeoutTime must be number !');
}
},
timeout() {
// 将 timeout 办法进行解决
// 进行类型查看,异样抛出谬误
if (!typeCheck(options.timeout, 'Function')) {throw new TypeError('param timeout must be function !');
}
function timeout() {function outTime() {
// 移除无用的 script 节点
script.parentNode.removeChild(script);
// 删除命名在全局的办法
window.hasOwnProperty(callbackName) && delete window[callbackName];
// 革除定时器
clearTimeout(timer);
// 执行超时函数
options.timeout();}
// 设置超时函数
timer = setTimeout(outTime, timeoutTime);
}
endMethods.push(timeout); // 超时函数放在队列中最初执行
}
};
// 遍历选项执行对应的办法
for (let item in options) {methods[item]();}
// 执行最初要执行的队列
endMethods.forEach(item => {item();
});
// 如果没有回调,并且申请 query 不为空的状况。兼容是否有问号状况
// warn url include data
if (noCallback && dataStr != '') {url.indexOf('?') == -1
? (url += `?${dataStr.slice(0, -1)}`)
: (url += `&${dataStr.slice(0, -1)}`);
}
// 有回调且兼容有无问号状况
if (!noCallback) {
// 增加全局办法
window[callbackName] = data => {
// 有胜利回调则执行,并且将参数传入
success && success(data);
// 移除 script 标签
oHead.removeChild(script);
// 移除全局办法
delete window[callbackName];
};
url.indexOf('?') == -1
? (url += `?${dataStr}${callback}=${callbackName}`)
: (url += `&${dataStr}${callback}=${callbackName}`);
}
// 对 url 编码
url = encodeURI(url);
// 给 script 标签增加加载实现回调
function loadLis() {
// 移除加载实现办法
script.removeEventListener('load', loadLis);
// 参数中有回调则执行回调
loaded && loaded();
// 革除定时器
clearTimeout(timer);
}
// 增加加载实现办法
script.addEventListener('load', loadLis);
// 将 url 赋值给 script 标签
script.src = url;
// 最初将 script 标签插入
oHead.appendChild(script);
}
以上就实现实现了一个残缺的 JSONP 网络申请库。
jsonp-pro
github: https://github.com/peng/jsonp…
npm: https://www.npmjs.com/package…