JSONP(JSON with Padding)是资料格式JSON的一种“使用模式”,可以让网页从别的网域获取资料。
由于浏览器同源策略,一般来说位于server1.a.com的网页无法与 server2.a.com的服务器沟通,而html的 <script>元素是一个例外。利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON资料,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器执行而不是用JSON解析器解析。
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-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-pro
npm: https://www.npmjs.com/package/jsonp-pro
来自:https://segmentfault.com/a/1190000042354982
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
什么是JSONP?先说说JSONP是怎么产生的?不管jQuery也好,ExtJs也罢,又或者是其他支持jsonp的框架,他们幕后所做的工作都是一样的,下面我来循序渐进的说明一下jsonp在客户端的实现
由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现。当然了,通过调用强大的PhoneGap插件然后打包,你可以实现100%的Socket通讯和本地数据库功能
jsonp是利用浏览器请求script文件时不受同源策略的限制而实现的,伪造一个script标签,将请求数据的url赋值给script的src属性,并将该标签添加到html中,浏览器会自动发送请求,返回的一般时一段js代码,即函数的调用代码。
一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题;jsonp不是ajax的一个特例,哪怕jquery等巨头把jsonp封装进了ajax,也不能改变!
JSONP与JSON只有一字之差,我们在使用Jquery的Ajax调用的时候也是使用相同的方法来调用,两者的区别几乎只在于使用的dataType这个属性的不同。但是实际上JSON和JSONP是完全不同的两个东西,JSON是一个数据格式
最近在看《你所不知道的javascript》[中卷]一书,第一部分是类型和语法。本文是基于这部分的产物。在强制类型转换->抽象值操作-> toString 部分,其中对工具函数 JSON.stringify(..) 将 JSON 对象序列化为字符串部分介绍进行了详细的介绍。
因为不想让再引用新的第三方组件了,所以执念了一下,于是搜索到了下面的代码,调试了一下,发现确实能用,但是存在一个缺陷,就是如果存在连续多次的请求,都会回调到同一个函数上
Jsonp(JSON with Padding) 是 json 的一种\\\"使用模式\\\",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!