写该系列文章的初衷是“让每位前端工程师掌握高频知识点,为工作助力”。这是前端百题斩的第25斩,希望朋友们关注公众号“执鸢者”,用知识武装自己的头脑。
25.1 同源策略
25.1.1 同源
跨域本质其实就是指两个地址不同源,不同源的反面不就是同源,同源指的是:如果两个URL的协议、域名和端口号都相同,则就是两个同源的URL。
// 非同源:协议不同
http://www.baidu.com
https://www.baidu.com
// 同源:协议、域名、端口号都相同
http://www.baidu.com
http://www.baidu.com?query=1
25.1.2 同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的加载的脚本如何能与另一个源的资源进行交互。其主要是为了保护用户信息的安全,防止恶意的网站窃取数据,是浏览器在Web页面层面做的安全保护。
25.1.3 同源策略的表现
既然同源策略是浏览器在Web页面层面做的保护,那么该层面哪些位置需要进行保护呢?总结下来主要包含三个层面:DOM层面、数据层面、网络层面。
DOM层面
同源策略限制了来自不同源的JavaScript脚本对当前DOM对象读和写的操作。
数据层面
同源策略限制了不同源的站点读取当前站点的Cookie、IndexedDB、localStorage等数据。
网络层面
同源策略限制了通过XMHttpRequest等方式将站点的数据发送给不同源的站点。
25.2 跨域分类
同源策略保证了浏览器的安全,但是如果将这三个层面限制的死死的,则会让程序员的开发工作举步维艰,所以浏览器需要在最严格的同源策略限制下做一些让步,这些让步更多了是在安全性与便捷性的权衡。其实跨域的方式就可以认为是浏览器出让了一些安全性或在遵守浏览器同源策略前提下所采取的一种折中手段。
25.2.1 DOM层面和数据层面分类
根据同源策略,如果两个页面不同源,无法互相操作DOM、访问数据,但是两个不同源页面之间进行通信是比较常见的情形,典型的例子就是iframe窗口与父窗口之间的通信。随着历史的车轮,实现DOM层面间通信的方式有多种,如下所示:
片段标识符
片段标识符其核心原理就是通过监听url中hash的改变来实现数据的传递,想法真的很巧妙。
// 父页面parentHtml.html
<!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
</head>
<body>
我是父页面
<button id='btn'>父传给子</button>
<iframe src="./childHtml.html" id="childHtmlId"></iframe>
</body>
<script>
window.onhashchange = function() {
console.log(decodeURIComponent(window.location.hash));
};
document.getElementById('btn').addEventListener('click', () => {
const iframeDom = document.getElementById('childHtmlId');
iframeDom.src += '#父传给子';
});
</script>
</html>
// 子页面childHtml.html
<!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
</head>
<body>
我是子页面
<button id='btn'>子传给父</button>
</body>
<script>
window.onhashchange = function() {
console.log(decodeURIComponent(window.location.hash));
};
document.getElementById('btn').addEventListener('click', () => {
parent.location.href += '#子传给父';
});
</script>
</html>
window.name
浏览器窗口有window.name属性,这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。如果需要实现父页面和跨域的子页面之间的通信,需要一个和父页面同源的子页面作为中介,将跨域的子页面中的信息传递过来。(好麻烦呀,强烈不推荐使用,此处就不写对应的代码啦)
document.domain
document.domain是存放文档的服务器的主机名,可通过手动设置将其设置成当前域名或者上级的域名,当具有相同document.domain的页面就相当于处于同域名的服务器上,如果其域名和端口号相同就可以实现跨域访问数据了。
postMessage(强烈推荐)
window.postMessage是HTML5新增的跨文档通信API,该API,允许跨窗口通信,不论这两个窗口是否同源。
// 父页面
<!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
</head>
<body>
我是父页面
<button id='btn'>父传给子</button>
<iframe src="http://127.0.0.1:5500/024/childHtml.html" id="childHtmlId"></iframe>
</body>
<script>
window.addEventListener('message', function(event) {
console.log('父页面接收到信息', event.data);
});
document.getElementById('btn').addEventListener('click', () => {
const iframeDom = document.getElementById('childHtmlId');
iframeDom.contentWindow.postMessage('我是执鸢者1', 'http://127.0.0.1:5500/024/childHtml1.html');
});
</script>
</html>
// 子页面
<!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
</head>
<body>
我是子页面
<button id='btn'>子传给父</button>
</body>
<script>
window.addEventListener('message', function(event) {
console.log('子页面接收到信息', event.data);
});
document.getElementById('btn').addEventListener('click', () => {
parent.postMessage('我是执鸢者2', 'http://127.0.0.1:5500/024/parentHtml1.html');
});
</script>
</html>
25.2.2 网络层面
根据同源策略,浏览器默认是不允许XMLHttpRequest对象访问非同一站点的资源的,这会大大制约生产力,所以需要破解这种限制,实现跨域访问资源。目前广泛采用的主要有三种方式(注:该出不给出具体代码,后续会有专门的百题斩进行详细阐述):
通过代理实现
同源策略是浏览器为了安全制定的策略,所以服务端不会存在这样的限制,这样我们就可以将请求打到同源的服务器上,然后经由同源服务器代理至最终需要的服务器,从而实现跨域请求的目的。例如可以通过Nginx、Node中间件等。
JSONP的方式(具体实现见后续百题斩)
JSONP是一种借助script元素实现跨域的技术,它并没有使用XMLHttpRequest对象,其能够实现跨域主要得益于script有两个特点:
(1)src属性能够访问任何URL资源,并不会受到同源策略的限制;
(2)如果访问的资源包含JavaScript代码,其会在下载后自动执行。
CORS方式(具体实现见后续百题斩)
跨域资源共享(CORS),该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。(实现一个跨域请求的方式,其中html访问网址为http://127.0.0.1:8009; 服务器监听端口为:8010)
(1)html页面内容
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test CORS</title>
</head>
<body>
CORS
<script src="https://code.bdstatic.com/npm/axios@0.20.0/dist/axios.min.js"></script>
<script>
axios('http://127.0.0.1:8010', {
method: 'get'
}).then(console.log)
</script>
</body>
</html>
(2)服务器端代码
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log('get请求收到了!!!');
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8009');
res.send('get请求已经被处理');
})
app.listen(8010, () => {
console.log('8010 is listening')
});
1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
2.关注公众号执鸢者,领取学习资料,定期为你推送原创深度好文
3.关注公众号进群,里面大佬多多,一起向他们学习
1. 前端百题斩[001]——typeof和instanceof
3. 前端百题斩【003-004】——从基本类型、引用类型到包装对象
6. 前端百题斩【007】——js中必须知道的四种数据类型判断方法
7. 前端百题斩【008-009】——从JavaScript的代码执行过程到函数执行过程
8. 前端百题斩【010】——通俗易懂的JavaScript执行上下文
10. 前端百题斩【012】——js中作用域及作用域链的真面目
12. 前端百题斩【014】——js中的这些“this”指向都值得了解
13. 前端百题斩【015】——快速手撕call、apply、bind
14. 前端百题斩【016】——原型、构造函数和实例之间的奇妙关系
15. 前端百题斩【017】——一基础、二主线、双机制理解原型链
18. 前端百题斩【020】——竟然有五种方式实现flat方法
20. 前端百题斩【022】——开拓思路之三种方式实现字符串转驼峰
22. 前端百题斩【024】——我从浏览器控制台看到了五种存储方式
23. 前端也要懂机器学习(下)
24. 学架构助力前端起飞
26. Vue源码思想在工作中的应用
27. 一文搞定Diff算法