本着学习和总结的态度写的技术输出,文中有任何错误和问题,请你们指出。更多的技术输出能够查看个人 github博客。javascript
整理了一些前端的学习资源,但愿可以帮助到有须要的人,地址: 学习资源汇总。html
跨域指的是协议(protocol ),域名(host),端口号(post)都不相同的资源之间尝试着进行交互通讯,而因为受浏览器同源策略的限制,没法正常进行交互通讯。前端
最多见的实际场景就是在项目开发过程当中,会存在请求第三方其余域下的资源,例如:使用地图 API 的时候,设置密钥的时候须要设置白名单才能正常使用地图 API。java
使用 AJAX 请求第三方不一样域下的数据资源的时候,若是不处理跨域问题,便不能成功发送 HTTP 请求,且浏览器会发出错误警告。node
MDN 解释: 同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。ios
浏览器的同源策略的目的就是为了防止 XSS,CSRF 等恶意攻击。git
同源策略的交互方式有三种:github
只有资源之间的协议,域名和端口号都相同,才是同一个源。web
下面是关于同源以及不一样源之间的跨域描述。ajax
URL | 说明 | 是否容许通信 |
---|---|---|
www.demo.com/a.html www.demo.com/b.html www.demo.com/c.html |
同一域名 | 容许 |
www.demo.com/news/a.html www.demo.com/center/b.ht… www.demo.com/server/c.ht… |
同一域名下的不一样文件夹 | 容许 |
www.demo.com/a.html www.demo.com:80/b.html |
不一样端口号 | 不容许 |
www.demo.com/a.html www.demo.com/b.html |
不一样协议 | 不容许 |
www.demo.com/a.html www.test.com/b.html |
不一样域名 | 不容许 |
www.demo.com/a.html test.demo.com/b.html |
主域相同,子域不一样 | 不容许 |
因为浏览器同源策略是容许 script 标签这样的跨域资源嵌套的,因此 script 标签的资源不受同源策略的限制。
JSONP 的解决方案就是经过 script 标签进行跨域请求。
<!-- 经过原生使用 script 标签 -->
<script> function jsonpCallback(data) { alert('获取到的数据了,打开控制台瞧瞧'); console.log(data); } </script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
复制代码
也可使用 AJAX GET 请求方式来跨域请求(axios GET 方式跨域同理)。
<!-- AJAX GET 请求 -->
<script> function jsonpCallback(data) { alert('获取到的数据了,打开控制台瞧瞧'); console.log(data); } $.ajax({ type: 'GET', // 必须是 GET 请求 url: 'http://127.0.0.1:3000', dataType: 'jsonp', // 设置为 jsonp 类型 jsonpCallback: 'jsonpCallback' // 设置回调函数 }) </script>
复制代码
优缺点:
兼容性好,低版本的 IE 也支持这种方式。
只能支持 GET 方式的 HTTP 请求。
只支持先后端数据通讯这样的 HTTP 请求,并不能解决不一样域下的页面之间的数据交互通讯问题。
CORS 跨域资源共享容许在服务端进行相关设置后,能够进行跨域通讯。
服务端未设置 CORS 跨域字段,服务端会拒绝请求并提示错误警告。
服务端设置 Access-Control-Allow-Origin 字段,值能够是具体的域名或者 '*' 通配符,配置好后就能够容许跨域请求数据。
<script>
$.ajax({
type: 'post',
url: 'http://127.0.0.1:3000',
success: function(res) {
alert('获取到的数据了,打开控制台瞧瞧');
console.log(res);
}
})
</script>
复制代码
服务端如何设置跨域字段? 后端语言设置跨域的方式都不一致,具体可参考后端语言自己的 API。
Node 端设置
res.writeHead(200, {
'Access-Control-Allow-Origin': '*'
});
// 或者使用了 Express 这样的框架
res.header("Access-Control-Allow-Origin", "*");
复制代码
关于 CORS 的详细,能够参考这篇笔记,CORS跨域资源共享。
经过服务端代理请求的方式也是解决浏览器跨域问题的方案。同源策略只是针对浏览器的安全策略,服务端并不受同源策略的限制,也就不存在跨域的问题。具体步骤以下:
// 服务端代理请求代码
// 服务端只是简单的经过正常的 HTTP 请求的方式来代理请求接口数据
// 或者也可使用 proxy 模块来代理,至于怎么使用 proxy 模块,待研究完善
var url = 'https://cnodejs.org/api/v1/topics';
https.get(url, (resp) => {
let data = "";
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=utf-8'
});
res.end(data);
});
})
复制代码
location.hash + iframe 跨域通讯的实现是这样的:
大体流程就是:
a 页面代码
<script>
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = "http://localhost:8081/b.html#data";
document.body.appendChild(iframe);
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
console.log('得到到的数据是:', data);
}catch(e) {}
}
window.addEventListener('hashchange', function(e) {
console.log('监听到hash的变化:', location.hash.substring(1));
})
</script>
复制代码
b 页面代码
<script>
switch(location.hash) {
case '#data':
callback();
break;
}
function callback() {
var data = "testHash"
try {
parent.location.hash = data;
}catch(e) {
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://localhost:8080/c.html#' + data;
document.body.appendChild(ifrproxy);
}
}
</script>
复制代码
c 页面代码
<script>
// 修改 a 页面的 hash 值
parent.parent.location.hash = self.location.hash.substring(1);
// 调用 a 页面的全局函数
parent.parent.checkHash();
</script>
复制代码
优缺点:
该方案只限于主域相同子域不一样的资源跨域解决方案。
实际应用场景:
以前的项目开发中,常常碰到这样的跨域问题,大体相似于在开发新产品的产品页中,在没有正式上线以前,通常都是上传到内部的测试环境中,好比测试环境的域名是 test.admin.com/xxx/xxx,而项目… consumer-test.admin.com/xxx/xxx 这样的,产品页是单独分离部署上线,再经过 iframe 嵌套到项目中。在内部测试过程当中,因为产品页测试环境和项目测试环境主域相同而子域不一样,且产品页中须要用到项目中定义的全局公共资源,因为跨域问题,这些公共资源是获取不到的。
这种场景的跨域解决方案就是利用 document.domain 设置。在产品页和项目中将 document.domain 设置成相同域就能够实现跨域,嵌套的产品页就能够访问父页面的公共资源了。须要注意的一点就是,document.domain 的设置是有限制的,只能设置成自身或者更高级的父域,且主域必须相同。
项目页面
<iframe src="test.admin.com/xxx/xxx"></iframe>
<script>
document.domain = 'admin.com';
</script>
复制代码
产品页
<script>
// 设置以后就可获取项目页面中定义的公共资源了
document.domain = 'admin.com';
</script>
复制代码
window.name 指的是当前浏览器窗口的名称,默认为空字符串,每一个窗口的 window.name 都是独立的。iframe 嵌套的页面中也有属于本身的 window 对象,这个 window 是top window 的子窗口,也一样拥有 window.name 的属性。
window.name 的独特之处在于当在页面设置 window.name 的值,其实就是至关于给这个窗口设置了名称,然后在这个窗口加载其余页面(甚至不一样域的页面),window.name 的值依然存在(若是没有从新设置那么值不会变化),而且 window.name 的值支持比较大的存储(2MB)。
例如: 随便找个页面打开控制台,给当前窗口设置名称。
window.name = 'test-name';
复制代码
设置好以后能够在这个窗口下跳转到其余页面
window.location = 'https://www.baidu.com';
复制代码
页面跳转到了百度首页,可是 window.name 的值依然是以前设置的值,由于是在一个窗口中跳转的页面,窗口名称并不会被修改。
具体的跨域解决方式以下。
http://localhost:8080/a.html 与 http://localhost:8081/b.html 跨域通讯,a 页面经过 iframe 嵌套 b 页面,b 页面中设置好 window.name 的值,因为是不一样域,a 页面不能直接访问到 b 页面设置的 window.name 的值,须要一个与 a 页面同域的中间页来代理做为 a 页面与 b 页面通讯的桥梁。
a.html
<script>
var data = null;
var state = 0;
var iframe = document.createElement('iframe');
iframe.src = "http://localhost:8081/b.html";
iframe.style.display = 'none';
document.body.appendChild(iframe);
// 第一次加载先加载 b.html,b.html 设置好了 window.name 的值
// 然后加载 c.html,c.html 的 window.name 的值就是以前 b.html 设置的值
// 同域的状况下,a.html 能够经过 iframe.contentWindow.name 获取到 b.html 中 windoa.name 的值
iframe.onload = function() {
if(state === 0) {
iframe.src = "http://localhost:8080/c.html";
state = 1;
}else if(state === 1) {
data = iframe.contentWindow.name;
console.log('收到数据:', data);
}
}
</script>
复制代码
b.html
<script>
window.name = '这是传递的数据';
</script>
复制代码
中间代理页,只须要跟 a 页面保持同域就能够了,例如: http://localhost:8080/c.html 。
postMessage 是 HTML5 的新特性,用于页面之间跨域通讯。
postMessage 方法接受两个必要的参数:
a.html
<iframe src="http://localhost:8081/b.html" style='display: none;'></iframe>
<script>
window.onload = function() {
var targetOrigin = 'http://localhost:8081';
var data = {
name: '武林外传',
time: 2005,
length: 81,
address: '同福客栈'
};
// 向 b.html 发送消息
window.frames[0].postMessage(data, targetOrigin);
// 接收 b.html 发送的数据
window.addEventListener('message', function(e) {
console.log('b.html 发送来的消息:', e.data);
})
}
</script>
复制代码
b.html
<script>
var targetOrigin = 'http://localhost:8080';
window.addEventListener('message', function(e) {
if(e.source != window.parent) {
return;
}
// 接收 a.html 发送的数据
console.log('a.html 发送来的消息:', e.data);
// 向 a.html 发送消息
parent.postMessage('哈哈,我是b页面,我收到你的消息了', targetOrigin);
})
</script>
复制代码
协议,域名,端口号不相同的资源之间相互通讯,就会产生跨域问题。
处于安全考虑,浏览器的同源策略限制了不一样域之间相互通讯。
JSONP,CORS,Server Proxy 跨域解决方式的应用场景都是用于先后端之间的数据通讯,其余跨域解决方案主要是解决窗口页面之间的数据通讯。
JSONP 只支持 GET 方式的 HTTP 请求。
CORS 跨域资源请求须要后端支持。
Server Proxy 直接让后端代理发送请求。
全部的跨域解决方案都有对应的 DEMO 实例,可在 DEMO 中查看。想要看运行效果,能够全局安装 http-server 模块。
npm install -g http-server
复制代码
本着学习和总结的态度写的技术输出,文中有任何错误和问题,请你们指出。更多的技术输出能够查看个人 github博客。
整理了一些前端的学习资源,但愿可以帮助到有须要的人,地址: 学习资源汇总。