跨域是前端再常见不过的问题了,下面主要针对跨域做一次总结,一次理清楚。
一、jsonp解决跨域
jsonp解决跨域问题的原理是:script不受同源策略的影响。
//前端代码:
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function callback(res) {
console.log(res)
}
</script>
<script src="http://127.0.0.1:3000?fn=callback"></script>
</body>
</html>
//服务器代码
var http = require('http');
var server = http.createServer();
var qs = require("querystring")
server.on("request", (req, res) => {
let params = qs.parse(req.url.split("?")[1])
let obj = {name:"张三", age :20}
res.end(params.fn + "(" + JSON.stringify(obj) +")")
})
server.listen(3000, () => {
console.log("服务器已打开")
})
jsonp实现跨域:前端通过script标签将函数以参数的形式传递给后台,后台通过解
析请求的字符串,拿到该函数,然后让该函数以执行的状态返回给前端,并且在其中
传入数据。
不过jsonp请求只能支持get请求方式。
二、document.domain + iframe实现跨域
此方案只适用于主域相同,子域不同的页面实现跨域。
父窗口的地址为 http://www.fordmc.com/a.html
<body>
<iframe src="http://ioc.fordmc.com/b.html"></iframe>
<script>
document.domain = "fordmc.com"
var name = "哈哈哈"
</script>
</body>
子窗口的地址是 http://ioc.fordmc.com/b.html
<body>
<script>
document.domain = "fordmc.com"
console.log(window.parent.name) //哈哈哈
</script>
</body>
总结:document.domain + iframe实现跨域原理是,当子域名不一致的情况下,
需要在父级和子级都需要使用document.domain强制设置基础主域名。然后在父级
中使用iframe将子集引入,这样在子集就能拿到父级的数据了。
三、location.hash + iframe实现跨域
存在两个页面(a, b),如果a,b页面同域,则可以直接进行数据通信。如果a, b不同
域时,可以通过location.hash + iframe来实现跨域数据传输。
a页面所在的域为:http://www.domain1.com/a.html
a页面的代码为:
<body>
<iframe src="http://www.domain2.com/b.html"></iframe>
<script>
let iframe = document.querySelect("iframe")
setTimeout(() => {
iframe.src = iframe.src + "#user=admin"
})
</script>
</body>
<body>
<script>
window.onhashchange = function(){
console.log(location.hash) //#user=admin
}
</script>
</body>
总结:上面两个页面处于不同的域中,在a页面中使用iframe引入了b页面,通过改
变b页面的hash值,来实现将数据传递给b页面的目的。同样b页面使用
onhashchange监听函数,监听hash值的改变。本例中hash值为user=admin,这样
就能拿到这个字符串,然后最终在b页面中解析,最终实现数据跨域传输。
四、window.name + iframe实现跨域
window.name存在这样一个特点就是在不同的页面下,或者是在不同的域下面其值都
是存在的,并且name的长度非常长(2MB)
a页面的地址是:http://www.domain1.com/a.html
<body>
<script>
var proxy = function (url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function () {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function (data) {
console.log(data)
});
</script>
</body>
b页面的地址为:http://www.domain1.com/b.html
中间代理页,与a.html同域,内容为空即可。
c页面的地址是:http://www.domain2.com/c.html
<script>
window.name = "张三"
</script>
总结:window.name + iframe实现的思路:本例中需要在a页面的中访问到c页面
的数据,但是由于跨域,无法进行访问。此时设置一个代理b页面,起到桥梁的作
用。首先在a页面中设置iframe并且首先将其的src值设置为页面c的地址。由于
iframe需要执行两次onload,此时我们可以在iframe第一次加载之后,改变其src
为页面b的地址。这样iframe上的window.name了,当改变src时,window.name也
不会改变。这样a和b就是同源的了,访问数据就不受限制了。最后需要销毁iframe。
五、postMessage实现跨域
postMessage是html5中新增的api,是window属性中位数不多的可以实现跨域的。
其存在两个参数,一个是发送的数据(使用JSON.stringify()来格式化一下)
第二个参数是:origin,表示为主机 + 协议 + 端口,"*"表示向全部的端口发送,
"/"表示向当前窗口同源的窗口发送。
a页面的地址为:http://www.domain1.com/a.html
<body>
<iframe src="http://www.domain2.com/b.html">
<script>
let iframe = document.querySelector("iframe")
iframe.onload = function(){
let obj = { name:"张三",age:20 }
iframe.contentWindow.postMessage(JSON.stringify(obj),"http://www.domain2.com")
//向domain2发送数据
}
//监听domain2发送来的数据
window.addEventListener("message", function(data) {
console.log(data) //"data"
})
</script>
</body>
b页面的地址是:http://www.domain2.com/b.html
<script>
window.addEventListener("message", function(data){
console.log(data) //{name:"张三",age:20}
window.parent.postMessage("data", "http://www.domain1.com")
})
</script>
六、跨域资源共享(CORS)
目前跨域使用CORS比较多,如果不需要携带cookie,则只需要在服务端设置Access-control-Allow-Origin即可,如果需要携带cookie,则见下文。
前端设置
1、原生的ajax
// 前端设置是否带cookie
xhr.withCredentials = true;
2、实例代码
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
node后台进行设置
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
/*
* 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
* 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
*/
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');