一般跨域听得比较多的方案是 Nginx
代理,CORS
,而 JSONP
和 iframe
的跨域解决往往只在背八股文的时候出现,而且老是只给 JSONP
的实际操作手段,老是找不着 iframe
的实际操作,所以这篇文章就是介绍如何基于 iframe
解决跨域
您可以在线查看完整的示例源代码
跨域
什么是跨域,跨域定义太多了,我直接介绍哪些场景会有跨域,当请求路径和当前页面路径存在以下情况
协议不同,域名不同,端口不同
location.hash
阅前注意
http://localhost:3001: server
服务端http://localhost:3000: client
客户端
这个方法主要是使用的 location.hash
去通信,iframe
的 src
属性不受跨域限制和中间页面解决(_vue-router
也有用到 location.hash
_)
解决方案如下,需要跨域的页面,简称 client.html
<!-- client.html -->
<body>
<button onclick="getUsers()">请求</button>
</body>
<script>
const getUsers = () => {
let clientIframe = document.getElementById("clientIframe");
if (!clientIframe) {
clientIframe = document.createElement("iframe");
clientIframe.id = "clientIframe";
clientIframe.style.display = "none";
clientIframe.src = "http://localhost:3001/data.html/#users";
document.body.appendChild(clientIframe);
} else {
clientIframe.src = "http://localhost:3001/data.html/#users";
}
window.addEventListener("hashchange", function (e) {
console.log("返回响应", location.hash.substring(1));
});
};
</script>
客户端封装一个包裹 iframe
的请求,方便重复请求
注:考虑到浏览器创建 DOM
的消耗过高,因此采用单一实例,有部分文章采用的是重复创建,由于作者眼界有限,如有错误望在评论区指出
其中 http://localhost:3001/data.html
是服务端提供的文件,由于和服务端同属一个域,因此在其中的请求不受跨域限制
<!-- http://localhost:3001/data.html -->
<body></body>
<script>
// 初次加载
switch (location.hash) {
case "#users":
callback("users");
break;
}
// 重复调用
window.addEventListener("hashchange", function (e) {
switch (location.hash) {
case "#users":
callback("users");
break;
}
});
/**
* iframe 跨域回调
* @param {string} path 请求路径
*/
function callback(path) {
try {
const xhr = new XMLHttpRequest();
xhr.open("GET", `http://localhost:3001/${path}`);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// do something
}
}
};
xhr.send();
} catch (e) {
console.log(e);
}
}
</script>
内嵌 iframe
在请求到数据以后需要怎样和客户端通信呢?
http://localhost:3001/data.html
和 http://localhost:3000
通信难题
这个问题的解决方案其实就是通过 location.hash
,每一个 iframe
拥有一个属性 parent
如果当前窗口是一个<iframe>
,<object>
, 或者<frame>
,则它的父窗口是嵌入它的那个窗口 - window.parent - MDN
但注意,由于 ie, chrome
的安全机制我们无法修改此处的 http://localhost:3001/data.html
的 parent.location.hash
,因此需要借助一个中间页面,以 http://localhost:3000/proxy.html
代称,根据跨域定义,它和 http://localhost:3000
同源,下面给张图捋一捋三个 html
的关系
proxy.html
内容如下
<body></body>
<script>
// 初次加载
parent.parent.location.hash = self.location.hash.substring(3);
// 重复调用
window.addEventListener("hashchange", function (e) {
parent.parent.location.hash = self.location.hash.substring(3);
});
</script>
http://localhost:3000/proxy.html
的 location.hash
和 http://localhost:3000/
的 location.hash
是一样的
所以在 data.html
中的 do something
修改成以下内容
<!-- http://localhost:3001/data.html -->
<body></body>
<script>
// ...
if (xhr.status === 200) {
// ie, chrome 下的安全机制无法修改 parent.location.hash
// 所以要利用一个中间的代理 iframe
let proxyIframe = document.getElementById("proxyIframe");
if (!proxyIframe) {
proxyIframe = document.createElement("iframe");
proxyIframe.id = "proxyIframe";
proxyIframe.style.display = "none";
proxyIframe.src =
"http://localhost:3000/proxy.html#" +
Math.floor(Math.random() * 100) +
xhr.response;
document.body.appendChild(proxyIframe);
} else {
proxyIframe.src =
"http://localhost:3000/proxy.html#" +
Math.floor(Math.random() * 100) +
xhr.response;
}
location.hash = "";
}
// ...
</script>
响应返回加上随机数的原因是为了 proxy.html
能够触发 hashchange
事件,注意,前面说过http://localhost:3000/proxy.html
的 location.hash
和 http://localhost:3000/
的 location.hash
是一样的,但在此处修改 proxyIframe
的 src
是不会影响 proxy.html
的 location.hash
同时也能触发 hashchange
事件,除此之外,客户端也需要按约定过滤随机数,即 self.location.hash.substring(3)
效果如下
总结
你可以不懂但是不可以不会,该用到 JSONP
和 iframe
的时候你就可以拥有这个方向的思路,就拿 JSONP
来说,我以前一直以为是无用的八股,但后面实习时遇到一个公共接口不能使用 CORS
也不能使用 Nginx
,想知道原因?原因如下
CORS:
公司老业务,运行很久很稳定,开发人员跑路了,只有上古文档,而且非同组沟通困难Nginx:
不能直接上,需要知道接口的具体部署地址和库,而且有引用鉴权,同组对接后端尝试自行补写Referrer
,但最终失败
后面使用的 JSONP
解决,而 iframe
我想也有它合适的适用场景,就像这篇文章中提到的利用 iframe
引用公共视频播放器