JSONP
正常的Ajax请求是会受到同源策略的限制,当然在现代浏览器上可以通过CORS来打破Ajax只能同源使用的限制。下面介绍的是另外一种方式JSONP
其实原理很容易理解:
1、动态创建一个script标签(因为脚本是可以跨域请求的)
2、请求的url上至少要包含一个请求参数用来告诉后台本地回调函数的名称,例如 /api/getRoom?id=123&callback=handler,后台通过读取callback字段的值就知道客户端的回调函数叫什么名字。当然这个字段的名称只是跟后台的一个约定而已,不一定非要取callback
3、后台返回的时候遵循下面的格式,例如handler({roomName: '101', id: '123'}),因为我们是通过创建script发起请求,对于浏览器来说,接受的数据其实就是一段js代码。
说完原理就说说JSONP存在的一些缺点
1、只能发送get请求
2、无法精确的知道请求是否成功,script上有一个onerror事件,但是浏览器的支持程度不高,因此一般都会设置一个定时器,如果超过限定时间都没有响应,则认为请求失败。
下面是自己写的一个jsonp.js,用法很简单,有兴趣可以参考下。因为是用es6写的,因此需要用babel编译一下。
下载地址:https://github.com/vivialex/jsonp
1 /** 2 * Created by rd_haiying_zheng on 2017/11/21. 3 * 4 * options { 5 * url: '', 6 data: {}, 7 callbackName: 'callback', 8 timeout: 60, //超时设置,超过限定时间则认为请求失败 9 onSuccess: null, 10 onError: null, 11 onComplete: null 12 * } 13 * 14 */ 15 function type (obj) { 16 let type = { 17 '[object Array]': 'array', 18 '[object Boolean]': 'boolean', 19 '[object Date]': 'date', 20 '[object Function]': 'function', 21 '[object Number]': 'number', 22 '[object Object]': 'object', 23 '[object RegExp]': 'regexp', 24 '[object String]': 'string' 25 }; 26 27 if(obj === null){ 28 return obj + ''; 29 } 30 return typeof obj === 'object' || typeof obj === 'function' ? type[Object.prototype.toString.call(obj)] || 'object' : typeof obj; 31 } 32 function isWindow(obj) { 33 return obj !== null && obj === obj.window 34 } 35 function isPlainObject (obj) { 36 return type(obj) === 'object' && !isWindow(obj) && Object.getPrototypeOf(obj) === Object.prototype 37 } 38 39 let extend = (function(){ 40 function extend(target, source, deep) { 41 for (let key in source) 42 if (source.hasOwnProperty(key)) { 43 if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { 44 if (isPlainObject(source[key]) && !isPlainObject(target[key])) 45 target[key] = {}; 46 if (isArray(source[key]) && !isArray(target[key])) 47 target[key] = []; 48 extend(target[key], source[key], deep) 49 } 50 else if (source[key] !== undefined) target[key] = source[key] 51 } 52 } 53 return function(target){ 54 let deep, args = Array.prototype.slice.call(arguments, 1); 55 if (typeof target === 'boolean') { 56 deep = target; 57 target = args.shift() 58 } 59 args.forEach(function(arg){ extend(target, arg, deep) }); 60 return target 61 } 62 })(); 63 64 class Jsonp { 65 constructor(opts) { 66 if (!isPlainObject(opts)) { 67 throw new TypeError('The type of opts must be plainObject') 68 } 69 70 let defaultOps = { 71 url: '', 72 data: {}, 73 callbackName: 'callback', 74 timeout: 60, 75 onSuccess: null, 76 onError: null, 77 onComplete: null 78 }; 79 80 !opts.data && (opts.data = {}); 81 if (!isPlainObject(opts.data)) { 82 throw new TypeError('The type of opts.data must be plainObject') 83 } 84 85 if (!opts.url) { 86 throw new TypeError('The url is required') 87 } 88 89 if (typeof opts.timeout !== 'number' || parseInt(Math.abs(opts.timeout)) <= 0) { 90 opts.timeout = 60; 91 } 92 93 if (typeof opts.callbackName !== 'string') { 94 this.callbackName = 'callback'; 95 } 96 97 this._$body = document.body || document.getElementsByTagName('body')[0]; 98 this._opts = extend(defaultOps, opts); 99 this._execute = false; 100 101 this._init(); 102 } 103 _init() { 104 this._setParams(); 105 106 let $script = document.createElement('script'); 107 $script.setAttribute('type', 'text/javascript'); 108 109 let cbName = 'jsonp_' + new Date().getTime(), 110 timer, 111 params = [], 112 data = this._opts.data; 113 114 for(let i in data) { 115 if (data.hasOwnProperty(i)) { 116 params.push(i + '=' + data[i]); 117 } 118 } 119 120 params.push(this._opts.callbackName + '=' + cbName); 121 params = params.join('&'); 122 123 let complete = () => { 124 typeof this._opts.onComplete === 'function' && this._opts.onComplete(); 125 this._$body.removeChild($script); 126 $script.onerror = null; 127 timer && clearTimeout(timer); 128 window[cbName] = null; 129 this._execute = true; 130 }, 131 error = () => { 132 if (this._execute) { 133 return; 134 } 135 136 typeof this._opts.onError === 'function' && this._opts.onError(); 137 complete(); 138 }; 139 140 window[cbName] = (data) => { 141 if (this._execute) { 142 return; 143 } 144 145 try { 146 data = JSON.parse(data); 147 } catch (e) { 148 149 } 150 151 typeof this._opts.onSuccess === 'function' && this._opts.onSuccess(data); 152 complete(); 153 }; 154 155 //浏览器不一定支持script的onerror事件,因此增加一个定时器,超过限定时间则默认请求失败 156 $script.onerror = error; 157 timer = setTimeout(error, this._opts.timeout * 1000); 158 159 $script.src = this._opts.url + '?' + params; 160 this._$body.appendChild($script); 161 } 162 _setParams() { 163 let url = this._opts.url, 164 obj = {}; 165 166 if (url.indexOf('?') !== -1) { 167 let arr = url.split('?'), 168 params = arr[1].split('&'); 169 170 this._opts.url = arr[0]; 171 172 params.forEach(item => { 173 item = item.split('='); 174 obj[item[0]] = item[1]; 175 }); 176 177 this._opts.data = extend({}, obj, this._opts.data); 178 } 179 } 180 } 181 182 export default function (opts) { 183 return new Jsonp(opts); 184 }