原理分析

跨域通信的手段,原理如下:

  1. 利用 script 标签的 src 属性实现跨域
  2. 通过将前端方法作为参数传递到服务端,服务端注入参数后返回,实现服务端向客户端通信
  3. 由于使用 script 标签的 src 属性,只支持 get 方法

实现流程

  1. 设定 script 标签

  2. callback定义了一个函数名,而远程服务端通过调用指定的函数并传入参数来实现传递参数,将fn(response)传递回客户端

$callback = !empty($_GET['callback']) ? $_GET['callback'] : 'callback';
echo $callback.'(.json_encode($data).)';
  1. 客户端接收到返回的 js 脚本,解析执行fn(response)

实现示例

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
(function (global) { 
var id = 0,
container = document.getElementByTagName("head")[0]
var jsonp = (options) => {
if(!options || !options.url) return

var scriptNode = document.createElement("script"),
data = options.data || {},
url = options.url,
callback = options.callback,
fnName = “jsonp” + id++

// 添加回调函数
data["callback"] = fnName

// 拼接 URL
var params = []
for (var key in data) {
params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
}
url = url.indexOf("?") > 0 ? (url + "&") : (url + "?")
url += params.join("&")
scriptNode.src = url

// 传递的是一个匿名回调,要执行的话,暴露为一个全局方法
global[fnName] = (ret) => {
callback && callback(ret)
container.removeChild(scriptNode)
global[fnName] && delete global[fnName]
}
scriptNode.onerror = () => {
callback && callback({error: "error"})
container.removeChild(scriptNode)
global[fnName] && delete global[fnName]
}

scriptNode.type = "text/javascript"
container.appendChild(scriptNode)

global.jsonp = jsonp
}
})(this)

使用:

1
2
3
4
5
6
7
jsonp({
url: '',
data: {},
callback: (ret) => {
...
}
})