HTML5提出了一個新的用來跨域傳值的方法,即postMessage(這個名字太通俗了所以你最好看看是不是自己寫過一個同名的把它覆蓋了)。幸運的是IE8就開始支援了。
我們假設有兩個網站,1.com與2.com,我在1.com的頁面上通過iframe或window.open或超連結開啟了一個2.com的網頁,此時我要在2.com上做操作的時候,給1.com傳值,讓1.com有所變化。這個過程就是個跨域的過程。
如果你對window.open熟,你就會知道通過window.open開啟的網頁(我們稱之為子網頁),可以通過window.opener物件,訪問到把它開啟的頁面(父網頁),這樣一來,呼叫父頁面的函式就是非常簡單的事了。但是,在跨域的條件下,window.opener就成了一個空物件,“沒有許可權”,瀏覽器會這麼告訴你。
比如,你的父頁面有個函式叫callback,然後你子頁面本可以這樣呼叫:window.opener.callback(),同域時能成功,跨域時就沒有許可權了。
但是,此時你呼叫window.opener.postMessage(),卻可以成功!
關於跨域傳值的痛苦我就不多說了,本文只說一些前兩天學習postMessage時瞭解到的重要知識點。
postMessage語法
window.postMessage(msg,targetOrigin) |
這裡我要專門說一下postMessage要通過window物件呼叫!因為這裡的window不只是當前window,大部分使用postMessage的時候,都不是本頁面的window,而是其他網頁的window!如:
1,iframe的contentWindow
2,通過window.open方法開啟的新視窗的window
3,window.opener
如果你使用postMessage時沒有帶window,那麼,當然,你就是用的本頁面的window來呼叫了它。
引數說明
msg
這就是要傳遞的訊息了。它可以是一切javascript引數,如字串,數字,物件,陣列,而不是和json一樣只侷限於字串,很強大吧?
targetOrigin
這個引數稱作“目標域”,注意啦,是目標域不是本域!比如,你想在2.com的網頁上往1.com網頁上傳訊息,那麼這個引數就是“http://1.com/”,而不是2.com.
另外,一個完整的域包括:
協議,主機名,埠號。如:http://g.cn:80/
獲取postMessage傳過來的訊息
要對postMessage傳來的訊息進行處理,就要在頁面上加一個onmessage事件。如:
1 2 3 4 |
window.addEventListener('message',function(e) { console.log(e.origin,e.data) ; alert('有資料傳來了!') ; }) |
要注意:最好是通過addEventListener或attachEvent來加入onmessage事件,而不要直接window.onmessage=function(){},因為有的瀏覽器這樣加會識別不了(如低版Firefox)
這個onmessage事件接受一個引數,就是程式碼裡的e,實際上他就是一個event物件。但他裡面有很明顯的3個引數與其他event物件不一樣,即:
1,data:顧名思義,是傳遞來的message
2,source:傳送訊息的視窗物件
3,origin:傳送訊息視窗的源(協議+主機+埠號).比如從2.com往1.com發了訊息,那麼1.com收到訊息時,e.origin就是2.com
最重要的就是data了,你可以用e.data取得他,然後做後續操作了。不過為了安全,你最好先判斷一下e.source和e.origin是不是正確來源,再作操作。
完整的postMessage流程示例
接著剛才的例子來,我著重講一個彈窗給父頁面傳訊息的例子。
我們的父頁面叫1.com,他上面有關鍵程式碼是:
1 2 3 4 |
window.addEventListener('message',function(e) { console.log(e.origin,e.data) ; alert('子頁面有資料傳來了!') ; }) |
就這麼簡單。而彈出視窗(2.com)上的程式碼是:
1 2 |
vardomain='http://1.com/' ; window.opener.postMessage({obj:'I am a obj'},domain ) |
還是很簡單吧!你看,跨域時本來不能訪問的window.opener可以訪問了——不過你直接呼叫父頁面的方法還是會失敗!就是這麼不講理。
其他應用場景的例子
1,必須要說明:通過超連結開啟的新視窗也能用window.opener來訪問其父頁面,程式碼跟上面一樣
2,iframe的例子
父頁面:
1 2 |
varo=document.getElementsByTagName('iframe')[0] ; o.contentWindow.postMessage('Hello World',"*");//向框架頁傳訊息 |
被框架包裹的子頁面:
1 2 3 4 5 6 7 8 9 |
window.addEventListener('onmessage',function(e) { if(e.domain=='1.com') { if(e.data=='Hello World') { e.source.postMessage('Hello',"*"); //反過來向父頁面傳訊息 }else { alert(e.data) ; } } }); |
這個例子充分說明了postMessage是雙向的。
3,window.open一個視窗,然後向他傳訊息
父頁面:
1 2 3 4 5 |
vardomain2='http://2.com/' ; varwin=window.open(domain2+'Portal/2.com.html','wwwwwwww' ) setTimeout(function() { win.postMessage('ddddddddd',domain2 ) },2000) |
子頁面:
1 2 3 |
window.addEventListener('message',function(e) { console.log(e.origin,e.data ) }) |
要注意父頁面中的setTimeout,也就是要延遲傳訊息,因為子頁面不可能瞬間載入完成,他的onmessage事件也就沒初始化成功,這時給他傳訊息,當然是收不到的了。
後記
1,IE8+雖然支援postMessage,但只支援iframe的方式,window.open開啟的新視窗之間,沒法用。直到IE10才有相關改進
2,如何在本地模擬跨域呢?在hosts檔案里加入:
127.0.0.1 1.com127.0.0.1 2.com
就可以模擬出2個不同的域名了。