提要
項目中與到iframe子頁面中需要通過top獲取在父頁面中的全局變量的需求,由於App部署的緣故,導致父頁面和iframe子頁面分別在不同的端口下,導致iframe跨域現象,通過查閱資料進行問題解決。
瀏覽器有一個同源策略,第一種限制就是不能通過ajax的方法去請求不同源的文檔。第二種限制是不能瀏覽器中不同域的框架之間是不能進行js的交互操作的。
不過有一點,不同框架之間(父子框架和同輩框架),是能夠獲取到彼此的window對象的,但是卻不能獲取到window對象的屬性和方法(html5中的的postMessage方法是一個例外,還有些瀏覽器比如ie6也可以使用top、parent等少數幾個屬性),總之,你可以當做是只能獲取到一個幾乎無用的window對象。比如,有一個頁面,它的地址是http://wwww.example.com/a.html, 在這個頁面里邊有一個iframe,它的src是http://example.com/b.html,很顯然,這個頁面與它里邊的iframe框架是不同域的,所以我們是無法通過在頁面中書協js代碼來獲取iframe中的東西的,同樣iframe中的內容也無法直接獲取到a.html中的內容。

實踐
根據博客中介紹的幾種方式進行實踐:
通過修改document.domain來跨子域
子域中無法獲取父域的數據的時候就可以利用document.domain都設置成相同的域名就可以完成。但是要注意的是,document.domain的設置是有限制的,我們把document.domain設置成自身或者更高一級的父域,且主域必須相同。例如:a.b.example.com中某個文檔的document.domaiin可以設成a.b.example.com、b.example.com、example.com中的任意一個,但是不可以設成c.a.b.example.com,因為這是當前作用於的子域,也不能設成baidu.com,因為主域已經不相同了。
這種方式用來獲取端口不同的跨域處理起來是很方便的:
//父域的運行環境是http://localhost:8087/ //同樣在部署在同一台服務器上的不同端口的應用也是適用的 <iframe src="http://localhost:8086/" id="iframepage" width="100%" height="100%" frameborder="0" scrolling="yes" onLoad="getData"></iframe> <script> window.parentDate = { "name": "hello world!", "age": 18 } /** * 使用document.domain解決iframe父子模塊跨域的問題 */ let parentDomain = window.location.hostname; console.log("domain",parentDomain); //localhost document.domain = parentDomain; </script>
//子域的運行環境是http://localhost:8086/ <script> /** * 使用document.domain解決iframe父子模塊跨域的問題 */ console.log(document.domain); //localhost let childDomain = document.domain; document.domain = childDomain; let parentDate = top.parentDate; console.log("從父域獲取到的數據",parentDate); // 此處打印數據為 // { // "name": "hello world!", // "age": 18 // } </script>
到這里就能夠把主域的數據傳遞給子域了。同樣也可以把子域的數據傳遞子域:
//子域的獲取到top之后給top上添加屬性 <script> let childDomain = document.domain; document.domain = childDomain; top.childData = { //獲取到top之后給top添加屬性 "name": "你好世界!", "age": 26 } </script>
//父域在iframe加載完成之后就可以獲取到子域添加的屬性 <iframe src="http://localhost:8086/" id="iframepage" width="100%" height="100%" frameborder="0" scrolling="yes" onLoad="getData"></iframe> <script> getData(){ console.log("子域傳遞給父域的數據",top.childData); // 此處打印數據 // { // "name": "你好世界!", // "age": 26 // } } </script>
這樣就可以完成父子組件之間的通訊了。
不過如果你想在http://www.example.com/a.html頁面中通過ajax直接請求http://example.com/b.html頁面,即使你設置了相同的document.domain也還是不行的,所以修改document.domain的方法只適用於不同子域的框架間的交互。
使用HTML5中新引入的window.postMessage方法來跨域傳遞數據
window.postMessage(message, targetOrgin)方法是html5新引進的特性,可以使用它來想其他的window對象發送消息,無論這個window對象是屬於同源或者不同源,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。
調用postMessage方法的window對象是指要接受消息的哪一個window對象,該方法的第一個參數message為要發送的消息,類型只能為字符串;第二個參數targetOrgin用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符*。
需要接收消息的window對象,可是通過監聽自身的message時間來獲取傳過來的消息,消息內容存儲在該事件對象的data屬性中。
上面所有的向其他window對象發送消息,其中就是指一個頁面有幾個框架的那種情況,因為每一個框架都有一個window對象。在討論第二種方法的時候,我們說過,不同域的框架間是可以獲取到對象的window對象的,而且也可以使用window.postMessage這個方法。
//父域的運行環境是http://localhost:8087/ <iframe src="http://127.0.0.1:8086/" id="iframepage" width="100%" height="100%" frameborder="0" scrolling="yes" onLoad="getData"></iframe> <script> getData(){ let iframe = document.getElementById('iframepage'); let win = iframe.contentWindow; win.postMessage(JSON.stringify(parentDate),"*"); } </script>
//子域的運行環境是http://127.0.0.1:8086/ /** * 使用postMessage解決iframe父子模塊跨域的問題 */ window.onmessage = function(e){ e = e || event; console.log("從父域獲取到的數據",JSON.parse(e.data)); // 此處打印的數據為 // { // "name": "hello world!", // "age": 18 // } }
這樣在任何一個域內都可以獲取到從父域傳遞的數據。通過postMessage來跨域傳遞數據還是比較直觀和方便的,但是缺點是IE6、IE7不支持,至於能不能用可以在 Can I use上進行驗證。目前看到的是IE11是部分支持,不過剛才的方法在IE11上驗證是能夠正常執行的。

