实现前端跨域的几种方式

        在前端用 ajax 请求一个远程服务器资源存在同源问题,即资源路径必须和执行请求的代码文件路径必须是同协议,同域名和同端口, 只要有一个不一样就是跨域访问。以下有几种方式可以实现请求不同源的资源:

利用JSONP方式(主流)

jsonp是利用html中 <script> 标签的src属性并不被同源策略所限制,可以获取任何服务器上的js脚本运行, 如下测试代码:

html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>jsonp</title>
<script type="text/javascript">
function hundler(data){
console.log(data.msg);
}
</script>
</head>
<body>
<!-- 假设这是一个和该文件跨域的资源 -->
<script type="text/javascript" src="http://wozien.com/js/test.js"></script>
</body>
</html>

服务器上的test.js:

1
2
3
4
5
var obj = {
msg: 'jsonp to cross origin'
};

hundler(obj);

        运行index.html,在本地的console输出 jsonp to cross origin。jsonp的实质是加载不同服务器上的js代码放到本地浏览器运行,但是我们要加载的不是*.js文件怎么办?

        可以在src传递一个本地的回掉函数名即可,告知服务器是本地哪个函数去执行response的东西,所以服务器结果返回为:回调函数名(响应的数据)

html代码:

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
<!DOCTYPE html>
<html>
<head>
<title>jsonp</title>
<script type="text/javascript">
// 异步加载js
function addScriptTag(src){
var script = document.createElement('script');
script.setAttribute('type','text/javascript');
script.src = src;
document.body.appendChild(script);
}

// 回调函数--可以处理后台响应的json数据
function hundler(data){
console.log(data);
}

window.onload = function(){
addScriptTag('http://wozien.com/php/jsonp.php?callback=hundler');
}
</script>
</head>
<body>
</body>
</html>

服务器上的代码:

1
2
3
4
<?php
$callback = $_GET['callback'];
echo $callback.'(\'hello jsonp\')';
?>

运行后在控制台输出hello jsonp。所以以上就是整个jsonp实现跨域请求的大概流程,想要请求更加复杂的数据,可以得服务器中把数据封装在json中来交换即可。

利用window.name属性

在浏览器同一窗口内加载不同的页面可以共享 window.name 的值,同时拥有读写权限。假如在 a.html 中设置 window.name 的值,两秒后跳转到 b.html , 在 b.html 可以获取到对应设置的值,如下测试代码:

a.html 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<title>window.name</title>
<script type="text/javascript">
window.name = 'this is from client message';
window.setTimeout(function(){
window.location = 'http://wozien.com/test/b.html';
},2000);
</script>
</head>
<body>
</body>
</html>

b.html 代码:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
alert(window.name);
</script>
</head>
<body>

</body>
</html>

所以,我们可以利用 <iframe>src 来加载远程页面,在远程页面的把后台的 json 数据存到 window.name, 就可以实现跨域请求了。这里需要注意的是跨域的iframe是不能操作他的 window对象的,所以要在远程页面加载完后请求一个同源页面即可,如下测试代码:

a.html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>window.name</title>
<script type="text/javascript">
function getData(){
var iframe = document.getElementById('proxy');
iframe.onload = function(){
alert(iframe.contentWindow.name);
}
// 该页面和a页面同源
iframe.src = 'c.html';
}
</script>
</head>
<body>
<!-- 请求跨域的页面 -->
<iframe id="proxy" src="http://wozien.com/test/b.html" style="display: none" onload="getData()"></iframe>
</body>
</html>

b.html 代码:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
// 这里可以通过ajax请求后台数据,写到window.name上
window.name = 'remote origin set window property'
</script>
</head>
<body>
</body>
</html>

利用postMessage

otherWindow.postMessage 是html5新提供的一个新api,可以让任意两个窗口进行通信。其中 otherWinodw 是指需要需要通信的window对象,可以是 <iframe> 的dom对象的 contentWindow 属性或者通过 window.open() 返回的窗口句柄。该函数有两个必须参数,第一个是要通信的数据, 可以是类型任意类型数据。第二个参数是要通信页面所在的源信息,其中 * 表示任意源, / 表示和当前页面同源,如下a页面利用iframe 加载b页面,并向它发送数据:

a.html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<html lang="en">
<head>
<meta charset="UTF-8">
<title>a</title>
</head>
<body>
<iframe src="./b.html" frameborder="0" onload="postMsg()"></iframe>

<script>
function postMsg() {
var otherWin = document.querySelector('iframe').contentWindow;
otherWin.postMessage('Hello, I am from a', '/');
}

window.onmessage = function(ev) {
console.log(ev.data);
}
</script>
</body>
</html>

在接收的页面可以在window对象上监听 message 事件, 回调函数提供一个事件对象,该对象有三个常用的属性。一个是 data 表示通信的数据, 第二个是 origin 表示发送数据方通过 postMessage 设置的源信息,第三个参数是 source 表示发送数据方的window对象。

b.html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html lang="en">
<head>
<meta charset="UTF-8">
<title>b</title>
</head>
<body>
<script>
window.addEventListener('message', function(ev) {
// 应该时刻检查源的信息
if(ev.origin !== 'http://localhost:8888')
return;

console.log(ev.data);
ev.source.postMessage('Yes, I know. I am from b', '/');
})
</script>
</body>
</html>

在控制台输出:

同理,当我们需要请求跨域的后台资源时,就可以通过另外一个页面去请求,通过 postMessage 相互通信。