JSONP的实现

Author Avatar
AppleSun 10月 03, 2016

JSONP 是解决跨域问题的一种方案,不同于 JSON,其并不是一种数据交换格式,而只是一种绕过跨域的技巧

JSONP

JSONP 的原理非常简单,为了克服跨域问题,利用没有跨域限制的 script 标签加载预设的 callback 将内容传递给 js。一般来说我们约定通过一个参数来告诉服务器 JSONP 返回时应该调用的回调函数名,然后拼接出对应的 js。已微博 API 为例,这个参数名是 _cb。

img

写一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function JSONP(url,params,callbackKey,callbackFn){
//在参数里限定 callback 的名字
params = params || {}
params[callbackKey] = "jsonpCallback"
//预留callbackFn
window.jsonpCallback = callbackFn
const paramKeys = Object.keys(params)
const paramString = paramKeys
.map(key => `${key}=${params[key]}`)
.join('&')
//插入 DOM 元素
const script = document.createElement('script')
script.setAttribute('src',`${url}?${paraString}`)
document.body.appendChild(script)
}

For Example:

1
2
3
4
5
6
7
8
9
JSONP({
url: 'http://s.weibo.com/ajax/jsonp/suggestion',
params: {
key: 'test',
},
callback(result){
console.log(result.data)
}
})

会在命令行看到 ["TEST", "特殊泰帮承"],注意这里新浪微博的 API 只支持 HTTP,所以我们只能在 HTTP 页面上测试。

同时进行多个请求

上面的流程有一个问题,就是在只有一个 JSONP 调用时它工作的很正常,但是当出现两个或者以上的请求,回调函数就会被覆盖,这样会出现混乱。为了解决这个问题,我们需要对所有的回调函数进行编码,并且在调用时告诉后端对应的独一无二的编号

除此之外,污染全局空间显然是个不明智的选择,这个问题解决起来倒是非常简单,扔到 http://JSONP.xxx 下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function JSONP({  
url,
params,
callbackKey,
callback
}) {
// 唯一 id,不存在则初始化
JSONP.callbackId = JSONP.callbackId || 1
params = params || {}
// 传递的 callback 名,和下面预留的一致
params[callbackKey] = `JSONP.callbacks[${JSONP.callbackId}]`
// 不要污染 window
JSONP.callbacks = JSONP.callbacks || []
// 按照 id 放置 callback
JSONP.callbacks[JSONP.callbackId] = callback
const paramKeys = Object.keys(params)
const paramString = paramKeys
.map(key => `${key}=${params[key]}`)
.join('&')
const script = document.createElement('script')
script.setAttribute('src', `${url}?${paramString}`)
document.body.appendChild(script)
// id 占用,自增
JSONP.callbackId++
}

JSONP({
url: 'http://s.weibo.com/ajax/jsonp/suggestion',
params: {
key: 'test',
},
callbackKey: '_cb',
callback(result) {
console.log(result.data)
}
})
JSONP({
url: 'http://s.weibo.com/ajax/jsonp/suggestion',
params: {
key: 'excited',
},
callbackKey: '_cb',
callback(result) {
console.log(result.data)
}
})

可以看到现在请求的都是 http://s.weibo.com/ajax/jsonp/suggestion?key=test&_cb=JSONP.callbacks[1] 这样的,然后得到的 js 也是 JSONP.callbacks1,这样就不会有冲突的问题,也不污染全局域。

URI编码

上面的代码仍然存在一个小问题,

1
2
3
4
5
6
7
8
9
10
11
JSONP({  
url: 'http://s.weibo.com/ajax/jsonp/suggestion',
params: {
a: '545&b=3'
b: '5',
},
callbackKey: '_cb',
callback(result) {
console.log(result.data)
}
})

会导致 a 的内容直接被拼写进字符串,导致覆盖了 b 的值,而用户真的只是想让 a 的值为 trdgd&b=2 而已。解决方案也简单,进行 URI 编码即可,encodeURIComponent(‘trdgd&b=2’) 的结果为 trdgd%26b%3D2。即将上面参数处理的部分改为

1
2
3
const paramString = paramKeys  
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&')

这里值得一提的是,由于最终的 URL 不能包含 ASCII 码以外的字符,所以其实当使用中文或者特殊字符时其实会被自动编码。而 +,空格,/,?,%,#,&,= 等字符在 URL 中则会出现歧义,只有手动编码后才能让服务器端正确解析。

转自知乎源链接