什么是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...