目前有三种最常见的解决方案:
- CORS,在服务器端设置几个响应头,如 Access-Control-Allow-Origin: *
- Reverse Proxy,在 nginx/traefik/haproxy 等反向代理服务器中设置为同一域名
- JSONP
1. 一个正常的请求: JSON
正常发请求时,curl 示例:
$ curl https://shanyue.tech/api/user?id=100
{
"id": 100,
"name": "shanyue",
"wechat": "xxxxx",
"phone": "183xxxxxxxx"
}
使用 fetch 发送请求,示例:
const data = await fetch("https://shanyue.tech/api/user?id=100", {
headers: {
"content-type": "application/json",
},
method: "GET",
}).then((res) => res.json());
请求数据后,使用一个函数来处理数据
handleData(data);
一个 JSONP 请求
JSONP,全称 JSON with Padding,为了解决跨域的问题而出现。虽然它只能处理 GET 跨域,虽然现在基本上都使用 CORS 跨域,但仍然要知道它,毕竟面试会问。
JSONP 基于两个原理:
- 动态创建
script,使用script.src加载请求跨过跨域 -
script.src加载的脚本内容为 JSONP: 即PADDING(JSON)格式
从上可知,使用 JSONP 跨域同样需要服务端的支持。curl 示例
$ curl https://shanyue.tech/api/user?id=100&callback=padding
padding({
"id": 100,
"name": "shanyue",
"wechat": "xxxxx",
"phone": "183xxxxxxxx"
})
对于正常的请求有何不同一目了然: 多了一个 callback=padding, 并且响应数据被 padding 包围,这就是 JSONP
那请求数据后,如何处理数据呢?此时的 padding 就是处理数据的函数。我们只需要在前端实现定义好 padding 函数即可
window.padding = handleData;
基于以上两个原理,这里实现一个简单 jsonp 函数:
<script type="module">
function stringify(data) {
const pairs = Object.entries(data);
const qs = pairs
.map(([k, v]) => {
let noValue = false;
if (v === null || v === undefined || typeof v === "object") {
noValue = true;
}
return `${encodeURIComponent(k)}=${noValue ? "" : encodeURIComponent(v)}`;
})
.join("&");
return qs;
}
const fetchJSONP = ({ url, onData, params }) => {
const callbackName = 'jsonpCallback';
const script = document.createElement('script');
script.src = `${url}?${stringify({ callback: callbackName, ...params })}`;
script.onerror = (e) => {
delete window[callbackName];
document.body.removeChild(script);
};
window[callbackName] = (result) => {
console.log(result);
// 清理 callback
delete window[callbackName];
document.body.removeChild(script);
};
document.body.appendChild(script);
};
// 发送 JSONP 请求
fetchJSONP({
url: "http://localhost:10010",
params: { id: 10000 },
onData(data) {
console.log("Data:", data);
},
});
</script>
服务器端代码
JSONP 需要服务端进行配合,返回 JSON With Padding 数据,代码如下:
// 简单的 JSONP 服务端示例
// 用法:GET http://localhost:3001/jsonp?callback=xxx
const http = require('http');
const url = require('url');
const qs = require("querystring");
const server = http.createServer((req, res) => {
const { pathname, query } = url.parse(req.url);
const params = qs.parse(query);
const callback = params.callback;
const data = { message: 'Hello from JSONP server!', time: Date.now(), ...params };
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(`${callback}(${JSON.stringify(data)})`);
});
server.listen(3002, () => {
console.log('JSONP server running at http://localhost:3002/jsonp');
});
返回结果

image.png
5. 总结 步骤 发生了什么
- 客户端创建 <script> 标签,请求跨域接口,并带上 callback=handleResponse
- 服务端接收请求,读取 callback 参数值为 handleResponse
- 服务端构造响应内容为:handleResponse({...}) 的字符串
- 浏览器加载并执行该脚本,相当于调用 handleResponse(data)
- 客户端处理数据,完成跨域通信