咱們先看下如下場景:
開啓兩個本地服務器,頁面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由灰變藍
關於跨域的就先聊到這啦~