好久没有更新博客了,前段时间在疯狂面试,最近工作了才有时间来写博客。 准备来讲讲面试里常问到的跨域处理吧。 说到跨域,我们可能会下意思的说出 jsonp,服务端配置 cors,node 配置代理等,再多了,我可能想不起来了。本篇本来打算只记录 postMessage 这种方式的,但光说这个显得太单薄了,所以一并都总结一下吧。
跨域是什么,为什么会跨域? 要想知道为什么,你得先知道什么是同源策略。同源策略是浏览器的一个安全协议,它限制了只有在同源的情况下才可以安全访问请求到数据,同源即同一个协议、域名、端口即是同源,比如:
http://localhost:8000
这里 http 就是协议 localhost 是域名 而 8000 就是端口 协议域名端口哪怕有一个不同,都会造成跨域导致无法正常去请求(否则都乱套了,大家都能随意访问对方的数据,都在裸奔,也就没有隐私而言了)。但我们在日常开发的时候,早已经前后端分离了,所以在联调接口的时候,就会有各种跨域问题。
JSONP
在那个用 jq 刀耕火种的那个年代,jq 就基于 xmlHttprequest 封装了 ajax 请求库,在请求配置项里可以直接配置jsonp:true来做跨域请求。原理是利用 script 标签加载不受跨域的影响,将请求放到 script 标签的 src 属性上。同时声明一个回调函数用于接收数据,在请求后面加上这个回调函数,服务端接收到请求后把参数放到回调函数里,本次 http 连接结束后会自动执行这个回调函数。通过这种方式,我们实现了跨域请求。
上才艺
function jsonp(src) {
const script = document.createElement("script");
script.src = `${src}?callback=onSuccess`;
document.head.appendChild(script);
script.onload = () => {
// 加载成功后自动删除该标签
document.head.removeChild(script);
};
}
同时声明一个onSuccess函数用于响应请求接收结果
function onSuccess(res) {
console.log(res);
}
以上:我们声明了一个 jsonp 函数用于跨域请求数据,并声明一个onSuccess函数用于接收请求数据;
服务端响应(这里以 node 为例)
随便拉个脚手架搭个 node 服务,创建一个路由用于测试 jsonp 跨域请求。
search.get("/test", async (ctx) => {
const cb = ctx.query.callback;
const result = {
code: 0,
data: {
name: "xxxxxx",
token: "xxxxxxxxxxxxxxx",
},
};
ctx.body = `${cb}(${JSON.stringify(result)})`;
});
获取请求里的 callback 参数,把结果放到这个 callback 方法里; 前端触发 jsonp 这个方法去请求/search/test 看看结果

可以看到已经可以成功请求到数据了。至此,通过 jsonp 方法来跨域我们已经了解了。但是一定要知道这种方式的弊端,为何我们现在都不用 Jsonp 来做跨域请求了呢? jsonp 它只支持 get 请求,因为浏览器加载 script 脚本是通过 get 方式来请求的,所以没法用 post 请求了。众所周知的是 get 和 post 的区别里 有一条特别重要的是 get 会直接把请求参数拼接到连接上,相对 post 来说这是不够安全的。
CORS
服务端通过配置请求头,允许所有的跨域请求; 这里还是以 node 服务为例,比如 Koa 库,我们建一个中间件用于处理跨域请求;
app.use(async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "*");
ctx.set(
"Access-Control-Allow-Headers",
"Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild"
);
ctx.set("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, OPTIONS");
if (ctx.method == "OPTIONS") {
ctx.body = 200;
} else {
await next();
}
});
在 koa 实例里 use 这个 middleware 这里主要是配置了 Access-Control-Allow-Origin 允许了所有请求都能访问; 关于Access-Control-Allow-Origin的解释看 mdn
node 代理
现代化前端工程,基本都是通过 node 来打包构建的。常见的如 webpack,vite 等,在解决跨域请求的时候,都提供了相关的配置来处理跨域问题。通过配置 proxy 代理,将请求转发到当前项目里进行请求。 原理是跨域只存在于浏览器端,也就是说只受浏览器的影响,而通过 node 层去请求则不会有跨域问题。所以在项目开发阶段,把跨域请求转发到当前项目的 node 层里去请求,就不会有跨域的问题。
禁用同源策略
既然你浏览器有相关的安全协议禁止跨域请求,那我也可以禁用你这个协议,这样我就无视跨域的影响能正常发出请求了。
找到 chrome 浏览器新建一个快捷方式,点击快捷方式右键属性,找到目标 在目标里加上 --allow-file-access-from-files --user-data-dir="C:\MyChromeUserData" --disable-web-security 点击应用并关闭窗口

c 盘根目录新建一个名叫MyChromeUserData的文件夹用于存储新建的这个浏览器快捷方式的信息
双击桌面上 chrome 新建的那个快捷方式,如果出现这样的提示,就说明成功了。

postMessage
页面跨域通信还可以通过 postMessage 来实现。这个场景是比如通过 window.open 打开一个小窗口或者页面加载一个 iframe,两者需要进行通信。但受跨域影响,两者是无法进行通信的,此时可以通过 postMessage 来实现通信。 上才艺
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<iframe src="http://localhost:3000/" id="iframe"></iframe>
<script type="module">
window.addEventListener('message', (e) => {
console.log(e.data);
});
</script>
</body>
</html>
页面的地址是http://127.0.0.1:8848/private/index.html,它加载了一个 iframe 同时 window 监听了 message 并输出结果
http://localhost:3000页面有一个 button,给这个 btn 添加一个点击事件,postMessage方法第一个参数是传输的数据,第二个是传输的页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>postmsg</title>
</head>
<body>
<button id="btn">notify parent</button>
<script>
document.getElementById("btn").addEventListener("click", () => {
parent.postMessage('this is postmessage', 'http://127.0.0.1:8848/private/index.html');
})
</script>
</body>
</html>
通过点击 btn 触发请求,输出 e.data,data 即是'this is postmessage'
其他的譬如通过 Nginx 反向代理来实现跨域请求等,这些可以自行去百度了。