我们知道,iframe 是 html 的一个标签,可以在页面中创建内联框架,它有个src 属性(可以指向文件地址,html,php等)可以选择内联框架的内容。window.name 是当前窗口的名字,每个 iframe 都有包裹它的window,而这个 window 是 top window 的子窗口,既然同为 window 对象,则其自然也有 window.name 的值

window.name 的神奇之处在于 name 的值在不同的页面甚至是不同的域名加载后依然存在,而且可以最大支持 2mb 的值

如此我们可以试想一下,假设我们在 A.html 页面下请求远程服务器的数据,我们可以在该页面下新建一个 iframe 标签,该 iframe 标签的 src 属性指向远程服务器的地址(与 script 和 img 标签类似,其 src 的访问不受跨域的限制),与此同时服务器端设置好 window.name 的值(该 iframe 的 contentWindow 的 name 值),然后在 A.html 里面读取该 iframe 的 window.name 的值,似乎我们的目的就能实现了

我们先编写服务器端,与先前一样,仍旧使用 nodejs ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var app = require('express')();
var http = require('http').Server(app);

app.get('/getData', function (req, res) {
var content =
'<h1>Welcome to window.name page</h1>' +
'<script type="text/javascript">window.name = "hello window.name"</script>';
res.send(content);
});

http.listen(3000, function () {
console.log('listening on *:3000');
});

接下去编写客户端代码 (A.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>getData</title>
</head>
<body>
<script type="text/javascript">
var iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/getData';
document.body.appendChild(iframe);
iframe.onload = function () {
console.log(iframe.contentWindow.name);
};
</script>
</body>
</html>

服务端运行结果如下:

此时:服务端 window.name 的值设置成功

客户端结果运行如下:  

但不幸的是报错了,究其原因是因为 A.html 页面和其内含的 iframe 标签的 src 若是不同源(此处端口号分别为3000 和 63342),那么我们还是没法操作 iframe 框架内的任何东西,所以这里我们就不能取到 iframe 的 name 值了,但是真的就此束手无策了吗,那不是的,另辟蹊径,加之前面说了,无论怎样加载,window.name 的值都不会发生变化,于是我们在 A.html 相同的目录下,新建一个 proxy.html 的空页面,此时我们修改客户端的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>getData</title>
</head>
<body>
<script type="text/javascript">
var iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/getData';
document.body.appendChild(iframe);
iframe.onload = function () {
iframe.src = 'http://localhost:63342/cros/proxy/proxy.html';
console.log(iframe.contentWindow.name);
};
</script>
</body>
</html>

我们可以在 iframe 加载完成的瞬间,改变该 iframe 的src 指向,使之与 A.html 同源,如此 A.html 页面便可以成功获取到 window.name 的值了。但此处还有一个问题,就是每次 onload 时事件触发时,我们重置了 iframe 的 src 指向,相当于重新加载了页面,如此又会触发 onload 事件,如此会造成页面不断地刷新,所以就有了我们下面的改进方式:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>getData</title>
</head>
<body>
<script type="text/javascript">
var iframe = document.createElement('iframe');
// 默认隐藏此 iframe 标签
iframe.style.display = 'none';
// 设置一个状态值,保证只触发一次 onload 事件
var state = 0;
document.body.appendChild(iframe);
iframe.onload = function () {
// 若是首次触发 iframe 的 onload 事件
if (state === 1) {
console.log('成功获取到跨域的值为:' + iframe.contentWindow.name);
iframe.contentWindow.document.writeln('信息发送成功');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = 'http://localhost:63342/cros/proxy/proxy.html';
}
};
iframe.src = 'http://localhost:3000/getData';
document.body.appendChild(iframe);
</script>
</body>
</html>

执行结果如下所示:

此时我们得到了异步调用的值:hello window.name,跨域成功

封装后的方法

对于那些对原理不是很感冒的童鞋,我们这里封装一个方法,以供调用:

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
<script type="text/javascript">
/**
* 功能 : 用于进行跨域请求
* @param proxy_url 代理 html 的地址
* @param target_url 发送请求的地址
* @param fn
*/
function crossDomain(proxy_url, target_url, fn) {
iframe = document.createElement('iframe');
iframe.style.display = 'none';
var state = 0;
iframe.onload = function () {
if (state === 1) {
fn(iframe.contentWindow.name);
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = proxy_url;
}
};
iframe.src = target_url;
document.body.appendChild(iframe);
}
</script>

用法如下:

  • 接发送请求的地址是:http://localhost:3000/getData
  • 代理文件的地址是:http://localhost:63342/cros/proxy/proxy.html
1
2
3
4
5
var target_url = 'http://localhost:3000/getData';
var proxy_url = 'http://localhost:63342/cros/proxy/proxy.html';
crossDomain(proxy_url, target_url, function (data) {
console.log(data);
});

PS : proxy 文件可以不存在,虽然会报 404 错误,但是不影响功能