最近面试中问到候选人 JSONP ,发现大多数候选人 JSONP 原理都可以回答正确,但是如果让写代码实现一个 JSONP 函数,有很多人都写不出来,或者是考虑不全面,写出来的代码没法使用。
接下来我们一起来看,如何实现一个高质量的 JSONP 。
首先先来说一下 JSONP 的原理:
JSONP 原理
全称 JSON with Padding,是解决跨域问题的一种方案。
由于同源策略的限制,浏览器只允许请求当前源(域名、协议、端口)的资源,而 HTML 的 script 元素是一个例外。利用 script 元素的这个开放策略,网页可以得到从其它来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。具体的实现上有几个关键点:
1、服务端返回的数据不是 JSON,而是 JavaScript,也就是说 contentType 为 application/javascript ,内容格式为callbackFunction(JOSN)
。
2、callbackFunction 需要注册在 window 对象上,因为 script 加载后的执行作用域是window作用域。
3、需要考虑同时多个 JSONP 请求的情况,callbackFunction 挂在 window 上的属性名需要唯一。
4、请求结束需要移除本次请求产生的 script 标签和window上的回调函数。
5、最好支持 Promise 。
代码实现
函数定义:
function jsonp ({url, data, callback}) {
}
url 是请求地址,data(Object类型) 是请求参数,callback(Function类型) 是回调函数。
使用方法:
jsonp({
url: 'url',
data: {
key1: 'value1'
},
callback (data) {
// data 是服务端返回的数据
}
})
常规实现
先写一个JSON转Query参数的Function
function objectToQuery(obj) {
const arr = [];
for ( var i in o) {
arr.push(encodeURIComponent(i)+ '=' +encodeURIComponent(o[i]));
}
return arr.join('&');
}
function jsonp ({url, data, callback}) {
const container = document.getElementsByTagName('head')[0];
const fnName = `jsonp_${new Date().getTime()}`;
const script = document.createElement('script');
script.src = `${url}?${objectToQuery(data)}&callback=${fnName}`;
script.type = 'text/javascript';
container.appendChild(script);
window[fnName] = function (res) {
callback && callback(res);
// 很多候选人漏掉clean这块
container.removeChild(script);
delete window[fnName];
}
script.onerror = function() { // 异常处理,也是很多人漏掉的部分
window[fnName] = function() {
callback && callback(
'something error hanppend!'
)
container.removeChild(script);
delete window[fnName];
}
}
}
Promise实现
在常规实现的基础上改造。
function jsonp ({url, data, callback}) {
const container = document.getElementsByTagName('head')[0];
const fnName = `jsonp_${new Date().getTime()}`;
const script = document.createElement('script');
script.src = `${url}?${objectToQuery(data)}&callback=${fnName}`;
script.type = 'text/javascript';
container.appendChild(script);
return new Promise((resolve, reject) => {
window[fnName] = function (res) {
// 很多候选人漏掉clean这块
container.removeChild(script);
delete window[fnName];
resolve(res);
}
script.onerror = function() { // 异常处理,也是很多人漏掉的部分
container.removeChild(script);
delete window[fnName];
reject('something error hanppend!');
}
}
})
}
希望大家都能找到一个好工作!
需要内推的可以加我微信:
https://u.wechat.com/EHv139tCcQo7uycdQ8hVNHk (二维码自动识别)