咱们先看下如下场景:
开启两个本地服务器,页面A为localhost:9800,其中嵌套了iframeB localhost:9000,页面A想使用页面B的数据,例如调用它的方法,会报如下错误javascript
如图所示,Protocols,domains,and ports must match. 译为:协议、主机和端口号必须符合,不然,就是跨域。html
下面咱们来具体谈谈。java
咱们都知道,浏览器有个同源策略,也就是这个策略,限制了两个源中的资源相互交互。chrome
Two pages have the same origin if the protocol, port (if one is specified), and host are the same for both pages.译为:若是两个页面有相同的协议、端口号和主机,那么这两个页面就属于同一个源。跨域
也就是说,只有同一个源才能够进行资源共享。浏览器
咱们来举几个例子,若是想和 http://www.test.com/index.html 进行通讯:安全
URL | Result | Reason |
---|---|---|
http://www.test.com/page/othe... | 容许 | |
http://www.test.com/index.js | 容许 | |
http://a.test.com/index.html | 不容许 | 子域不一样 |
http://www.other.com/index.html | 不容许 | 主域不一样 |
https://www.test.com/index.html | 不容许 | 协议不一样 |
http://www.test.com:3000/index.html | 不容许 | 端口不一样 |
咱们能够看到,协议、端口、主机缺一不可,必须彻底匹配,上文就是因为端口号不一样而报错。服务器
那为何要有同源策略的限制呢?缘由也很简单,就是为了保证用户信息的安全,防止恶意的网站窃取数据。cookie
试想一下,若是咱们能够随意访问一个网站的cookie,那么岂不是随意窃取别别人登录的cookie?若是没有同源策略,那互联网就太危险了。app
同源策略的限制范围有如下几种:
而跨域访问,也无非两种状况:一是请求不一样源下的接口(如上第3种);二是请求不一样源的页面的资源(如上一、2,一般是页面中嵌套不一样源的iframe要进行通讯)。本文主要讨论第二种状况下形成的跨域,方法不少,主要介绍如下几种:document.domain
、location.hash
、window.name
、postMessage
。
适用于:主域相同子域不一样的页面,例如上例中的第三种
方法:只需将这两个页面的document.domain
设置为相同的父域名,便可实现不一样子域名之间的跨域通讯。
<!-- http://www.test.com/index.html --> <button>发送消息</button> <div></div> <iframe name="ifr" src="http://a.test.com/index.html" style="width: 100%;"></iframe> <script type="text/javascript"> document.domain = 'test.com'; function sendMsg(msg) { document.querySelector('div').innerHTML = msg; } document.querySelector('button').onclick = function (){ window.frames['ifr'].sendMsg('Hello son'); } </script>
<!-- http://a.test.com/index.html --> <div></div> <script type="text/javascript"> document.domain = 'myapp.com'; function sendMsg(msg) { document.querySelector('div').innerHTML = msg; parent.sendMsg('Hello father'); } </script>
通常来讲,URL的任何改变都从新会加载一个新的网页,除了hash
的变化,hash
的任何改变都不会致使页面刷新。
在跨域解决方案中,hash
也很是有用,来自不一样源的页面能够相互设置对方的 URL,包括hash
值,但不能获取对方的hash
值。文档之间能够经过hash
来相互通讯。
流程(如上图):
主页面A
中嵌入iframeB
,两个来自不一样域主页面A
中,将想要传递给B的字段,做为hash,将它与B的url链接起来,而后将B的src设置为链接后的urliframeB
中,就能够经过获取本身url的hash值,从而获得主页面传递的值,但在iframeB中,需设置一个定时器监听hash值的变化除了设置定时器,还能够经过监听window.onhashchange
事件
例子:启动两个本地服务器,主页面localhost:9800
,子页面localhost:9000
,主页面向子页面发消息
<!-- 主页面 localhost:9800 --> <script type="text/javascript"> function sendMsg(msg) { var data = encodeURI(JSON.stringify({msg: msg})); var target = "http://localhost:9000"; var src = target + "#" + data; document.getElementById("ifr").src = src; } function onClick() { sendMsg("来自'localhost: 9800': 你好, 这是你要的数据"); } </script> <body> <iframe id="ifr" src="http://localhost:9000" style="width: 100%"></iframe> <button onclick="onClick()">发送消息</button> </body>
<!-- 子页面 localhost:9000 --> <script type="text/javascript"> var oldHash = ""; function checkHash() { var newHash = location.hash.length > 1 ? location.hash.substring(1) : ''; if (newHash != oldHash) { oldHash = newHash; var msg = JSON.parse(decodeURI(newHash)).msg; document.getElementById("container").innerText = msg; clearInterval(timer); } } //设置定时器监听 hash 的变化 var timer = setInterval(checkHash, 1000); </script> <body> <div id="container"></div> </body>
结果以下:点击button后,子页面将收到主页面的消息
注意:使用hash
时最好对其进行编码、解码,不然在Firefox
中会报错。由于Firefox
会自动将hash
值进行编码,若是不进行解码就没法JSON.parse()
.
两个页面不一样源的状况下,IE、Chrome不容许修改parent.location.hash
的值(Firefox能够),因此若是主页面想从子页面获取消息,只能借助一个代理iframe设置。
流程以下(两种):
子页面
建立隐藏的代理iframe,与主页面同源,并将消息做为hash
,设置到iframe的src中代理页面
将主页面的hash
值设置为自身的hash
主页面
使用定时器监听hash
的变化例子以下
<!-- 主页面 localhost:9800 --> <script type="text/javascript"> var oldHash = ''; function checkHash() { var newHash = location.hash.length > 1 ? location.hash.substring(1) : ''; if (newHash != oldHash) { oldHash = newHash; var msg = JSON.parse(decodeURI(newHash)).msg; document.querySelector("div").innerText = msg; clearInterval(timer); } } //设置定时器监听 hash 的变化 var timer = setInterval(checkHash, 1000); </script> <body> <iframe id="ifr" name="ifr" src="http://localhost:9000" style="width: 100%;"></iframe> <div></div> </body>
<!-- 子页面 localhost:9000 --> <script type="text/javascript"> function sendMsg(msg) { try { parent.location.hash = encodeURI(JSON.stringify({msg: msg})); } catch(e) { //因为IE, Chrome的安全机制 没法修改 parent.location.hash //所以建立代理页面, 与主页面同源, 而后修改代理页面的hash var ifrProxy = document.createElement("frame"); ifrProxy.style.display = "none"; ifrProxy.src = "http://localhost:9800/public/proxy.html" + '#' + encodeURI(JSON.stringify({msg: msg})); document.body.appendChild(ifrProxy); } } function onClick() { sendMsg("Hello Father"); } </script> <body> <button onclick="onClick()">发送消息</button> </body>
<!-- 代理页面 localhost:9800/public/proxy.html --> <script type="text/javascript"> parent.parent.location.hash = window.location.hash.substring(1); </script>
结果如图:
这种方法的劣处就是将消息暴露在url中,所以也能够采用下文将讲述利用代理iframe跨域的方法,这边就不赘述了。
因为如今许多网站的hash
已经被用于其余用途,此时想用hash
跨域就会比较复杂了。这种状况下,咱们可使用一个同域的代理页面来完成
页面A想向页面B发送数据,流程以下:
页面A
建立一个隐藏的代理iframeC
,这个iframe与页面B
同域页面A
中,将要发送的数据,做为hash,与页面C
的url
链接起来代理iframeC
中,它与B同域,能够直接调用页面B
中的方法,这样即可以将hash值传递给B了例子以下
<!-- 页面A localhost:9800 --> <body> <iframe name="ifr" src="http://localhost:9000" style="width: 100%;"></iframe> <button onclick="onClick()">发送消息</button> <div></div> <script type="text/javascript"> function sendMsg(msg) { //建立隐藏的iframe, 与页面B同源 var frame = document.createElement('frame'); var target = 'http://localhost:9000/static/page/proxy.html'; var data = {frameName: 'ifr', msg: msg}; frame.src = target + "#" + encodeURI(JSON.stringify(data)); frame.style.display = 'none'; document.body.appendChild(frame); } function onClick() { sendMsg('你好, 我是localhost: 9800'); } </script>
<!-- 代理页面C localhost:9000/static/page/proxy.html --> <script type="text/javascript"> var hash = location.hash.length > 1 ? location.hash.substring(1) : ''; if (hash) { var data = JSON.parse(decodeURI(hash)); //处理数据 parent.frames[data.frameName].receiveData(data.msg); } </script>
<!-- 页面B localhost:9000 --> <script type="text/javascript"> function receiveData(msg) { document.querySelector('div').innerHTML = "接收到数据:"+ msg; } </script> <body> <div></div> </body>
结果以下:
缺点:
加载任何页面 window.name 的值始终保持不变
当页面A想从页面B中获取数据时:
页面A
,建立一个隐藏的iframeC
,将C的src
指向页面B
页面C
加载完成后,把响应的数据附加到window.name
上src
设为任何一个与A同源的页面
,这时 A 就能获取到 B 的name
属性值例子,启动两个本地服务器,页面Alocalhost:9800
,页面Blocalhost:9000
,页面A想从页面B中获取数据
<!-- 页面A localhost:9800 --> <script type="text/javascript"> function sendMsg(msg) { var state = 0, data; //1. 建立隐藏的iframe, 与页面B同源 var frame = document.createElement("frame"); frame.src = "http://localhost:9000"; frame.style.display = "none"; frame.onload = function () { if (state === 1) { //3. 此时iframe与页面A同源, 页面A能够获取到数据 data = frame.contentWindow.name; document.querySelector('.res').innerHTML = "响应数据:" + data; //4. 删除iframe frame.contentWindow.document.write(''); frame.contentWindow.close(); document.body.removeChild(frame); } else { //2. iframe加载完成后, 响应的数据已附加到此iframe上, 再将其导航至与页面A同源 state = 1; frame.src = "http://localhost:9800/test.html"; } }; document.body.appendChild(frame); } function onClick() { var val = 'hi, 我是页面A'; sendMsg(val); document.querySelector('.val').innerHTML = "请求数据:" + val; } </script> <body> <button onclick="onClick()">发送消息</button> <div class="val"></div> <div class="res"></div> </body>
<!-- 页面B localhost:9000 --> <script type="text/javascript"> window.name = 'hi, 我是页面B'; </script>
结果如图
另外,两个页面还可相互通讯
页面A经过hash
,将数据传递给页面B,页面B仍经过window.name
向页面A传递数据
场景以下:页面B存储了一些人的信息,页面B经过页面A的输入,获取不一样人的信息
<!-- 页面A localhost:9800 --> <script type="text/javascript"> function sendMsg(msg) { var state = 0, data; //1. 建立隐藏的iframe, 与页面B同源 var frame = document.createElement("frame"); frame.style.display = "none"; //将获取的数据经过hash传递给页面B frame.src = "http://localhost:9000" + "#" + encodeURI(JSON.stringify({data: msg})); frame.onload = function () { if (state === 1) { //3. 此时iframe与页面A同源, 页面A能够获取到数据 data = JSON.parse(frame.contentWindow.name); var html = ''; for (var key in data) { html += key + ": " + data[key] + " "; } document.querySelector('.res').innerHTML = html; //4. 删除iframe frame.contentWindow.document.write(''); frame.contentWindow.close(); document.body.removeChild(frame); } else { //2. iframe加载完成后, 响应的数据已附加到此iframe上, 再将其导航至与页面A同源, state = 1; // !!!注意 '/test.html'不可缺乏,不然在firefox不会触发frame.onload, 在chrome虽会触发但会报错 frame.src = "http://localhost:9800/test.html"; } }; document.body.appendChild(frame); } function onClick() { var val = document.querySelector('.text').value; sendMsg(val); document.querySelector('.val').innerHTML = "id为 " + val + " 的信息以下:"; } </script> <body> <input class="text" type="text" /> <button onclick="onClick()">发送消息</button> <div class="val"></div> <div class="res"></div> </body>
<!-- 页面B localhost:9000 --> <script type="text/javascript"> var information = { '1': { name: '狐狸', age: 2 }, '2': { name: '馒头', age: 1 }, '3': { name: '端午', age: 3 } }; var hash = location.hash.length > 1 ? location.hash.substring(1) : ''; if (hash) { //获取hash值 并进行转换 var obj = JSON.parse(decodeURI(hash)); var data = obj.data; //传递的数据为object, 需进行JSON转换 window.name = JSON.stringify(information[data]); } </script>
输入想得到的数据的id值,便可获得相应的信息
总结:
优势:容量很大,能够放置很长的字符串(2M左右,比url大得多)
缺点:必需要监听window.name属性的变化
HTML5规范中的新方法window.postMessage()
能够用于安全跨域通讯。当该方法调用时,将分发一个消息事件。对应的窗口经过事件监听来获取消息。
语法 :otherWindow.postMessage(message, targetOrigin)
otherWindow
表明其余窗口的引用,例如iframe的contentWindow
属性,经过window.open
返回的窗体,经过window.frames[]
返回的ifame对象。
message
表示发送给其余窗口的数据
targetOrigin
指定哪些来源的窗口能够接收到消息事件,其值能够是字符串"*"(表示无限制)或"/"(表示与父窗口同源)或一个URI。发送消息时,只有目标窗口的协议
、主机
、端口
这三项都匹配targetOrigin提供的值,消息才会发送。
接收消息的窗口能够经过监听message事件
,去接收消息
window.addEventListener("message", receiveMessage, false);
receiveMessage
为接收到消息后的操做
message事件的事件对象event
,提供三个属性:
event.source
:发送消息的窗体(能够用来引用父窗口)event.origin
:消息发向的网址(能够过滤不是发给本窗口的消息)event.data
:消息内容例子:启动两个本地服务器,页面Alocalhost:9800
,页面Blocalhost:9000
,页面A根据页面B发来的数据改变颜色
<!-- 页面A localhost:9800 --> <body> <iframe name="ifr" src="http://localhost:9000" style="width: 100%; display: none"></iframe> <div style="width: 200px; height: 200px; background-color: #ccc"></div> <button onclick="onClick()">改变颜色</button> <script type="text/javascript"> function onClick() { var otherFrame = window.frames["ifr"]; otherFrame.postMessage("getColor", "*"); } function handleReceive(event){ //判断来源 if(event.origin != "http://localhost:9000") return; //处理页面B发送的数据 var data = JSON.parse(event.data); document.querySelector('div').style.backgroundColor = data.color; } window.addEventListener("message", handleReceive, false); </script>
<!-- 页面B localhost:9000 --> <script type="text/javascript"> function handleReceive(event){ //判断来源 if (event.origin != "http://localhost:9800") { return; } if(event.data == "getColor"){ //给页面A发送数据 var targetWindow = parent.window; targetWindow.postMessage(JSON.stringify({color:'blue'}), "http://localhost:9800"); } } window.addEventListener("message", handleReceive, false); </script>
结果以下:点击按钮后,页面的div由灰变蓝
关于跨域的就先聊到这啦~