What is JSONP
JSONP (JSON with Padding) is a "usage pattern" of the data format JSON, which allows web pages to obtain data from other domains.
Due to the browser's same-origin policy, in general web pages located at server1.a.com cannot communicate with servers at server2.a.com, with the exception of the HTML <script> element. Using this open strategy of the <script> element, web pages can obtain JSON data dynamically generated from other sources, and this usage pattern is the so-called JSONP. The data captured with JSONP is not JSON, but arbitrary JavaScript, executed with a JavaScript interpreter instead of parsing with a JSON parser.
JSONP implementation principle
We understand the concept of jsonp, so how to implement it?
We want to get a piece of JSON data without cross-domain problem, we can use xhr GET method, assuming the request address is under the current domain /apis/data?id=123
, the current domain name is example.com;
return data is
{
name=peng,
age=18
}
When the data we obtain is not in the same domain, for example, the above example requests that the domain name be changed to example2.com, the domain name used by the page is still example.com, and the JSONP method is used to cross domains.
According to the above principle introduction, first we have a function in the global life.
window.jsonp1 = function(data) { console.log(data) }
Dynamically insert script tags into head tags
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标签
Finally, the content that needs to be returned by the script tag is a method call and the passed parameter is the content to be returned.
jsonp1({ name=peng, age=18 })
The above is a simple implementation of the JSONP principle.
Really implement a JSONP network request library
The above has a preliminary understanding of the principle and implementation of JSONP. If we want to use it in daily projects, we need to encapsulate a complete JSONP network request library.
Let's do a source code analysis of the jsonp-pro network request library, from which we understand and learn how to encapsulate a JSONP network request library.
Let's first take a look at the general method method library of requests
// 检查类型的方法,用于对方法传入类型的限制
/**
* 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 };
main file, main method
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);
}
The above completes the implementation of a complete JSONP network request library.
jsonp-pro
github: https://github.com/peng/jsonp-pro
npm: https://www.npmjs.com/package/jsonp-pro
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。