Fork me on GitHub

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 }

 

  

 

posted @ 2017-11-23 11:08  Alex_Zheng  阅读(292)  评论(0编辑  收藏  举报