HTML5 postMessage 跨域交換數據

前言

  以前簡單講解了利用script標籤(jsonp)以及iframe標籤(window.namelocation.hash)來跨域交換數據,今天咱們來學習一下HTML5的api,利用postMessage來跨域交換數據。和前面一些方式交換數據方式不一樣的是,利用postMessage不能和服務端交換數據只能在兩個窗口(iframe)之間交換數據,廢話很少說,咱們直接進入實戰。javascript

實戰postMessage

  • overview

  上文中說,postMessage是用於兩個窗口(iframe)之間交換數據的,若是咱們同時打開着百度和谷歌兩個頁面,是否是說這二者之間就能夠通訊了?No,no,no,事實並不是如此,就算百度和谷歌倆頁面有通訊的意願也不行。兩個窗口能通訊的前提是,一個窗口以iframe的形式存在於另外一個窗口,或者一個窗口是從另外一個窗口經過window.open()或者超連接的形式打開的(一樣能夠用window.opener獲取源窗口);換句話說,你要交換數據,必須能獲取目標窗口(target window)的引用,否則兩個窗口之間毫無聯繫,想通訊也無能爲力。html

  既然是H5家族的,咱們也得觀望下它被廣大瀏覽器的接受程度(具體細節check can I use postMessage),能夠看到接受程度仍是至關高的:html5

  而postMessage的使用方式也至關簡單:java

otherWindow.postMessage(message, targetOrigin, [transfer]);

  otherWindow是對接收方窗口的引用,通常能夠是如下幾種方式:web

window.frames[0].postMessage
document.getElementsByTagName('iframe')[0].contentWindow
window.opener.postMessage
event.source.postMessage
window.open 返回的引用
...

  而message顧名思義就是發送的數據內容,支持字符串、數字、json等幾乎全部形式的數據(詳見The structured clone algorithmjson

  targetOrigin是接收方的URI(協議+主機+端口),也能夠是url形式,但以後的內容(形如xx.html)會自動忽略;用通配符*能夠指定全部域,可是切記不要用(for security)。api

  transfer可省略,沒看懂是啥意思...之後有須要的時候再研究跨域

  而接受方窗口通常監聽message事件,詳見下面的例子。瀏覽器

  • window <-> iframe

  假設index頁面有個iframe(不一樣源),咱們要給iframe發送數據,而iframe獲得數據後也發送數據給top window,表示「我"獲得數據了。直接看源碼(思考如何發送and如何接收):安全

<!-- http://localhost:81/fish/index.html -->
<script type="text/javascript">
  // 頁面加載完後才能獲取dom節點(iframe)
  window.onload = function(){
    // 向目標源發送數據
    document.getElementsByTagName('iframe')[0].contentWindow.postMessage({"age":10}, 'http://localhost:8080');
  };

  // 監聽有沒有數據發送過來
  window.addEventListener('message', function(e) {
      console.log(e);
  });
</script>
<iframe src="http://localhost:8080/index.html"></iframe>
<!-- http://localhost:8080/index.html -->
<script type="text/javascript">
  // 監聽有沒有數據發送過來
  window.addEventListener('message', function(e){
      // 判斷數據發送方是不是可靠的地址
      if(e.origin !== 'http://localhost:81')
        return;
    // 打印數據格式
    console.log(e);
    // 回發數據
    e.source.postMessage('hello world', e.origin);
  }, false);
</script>

  咱們截圖看看打印的東西究竟長什麼樣(index頁面傳給iframe的數據):

  紅框標出的是三個最重要的屬性,data顧名思義就是傳輸的數據了;origin就是發送消息窗口的源(URI 協議+主機+端口);而source就能引用發送消息的窗口對象(能夠用它來引用發送窗口進行消息回傳)。

   在消息接收端監聽能夠監聽message事件(代碼如上),固然若是要兼容坑爹的ie確定要用attachEvent。這裏不推薦使用window.onmessage,兼容性不大好(好比不能兼容低版本ff)。

  • window <-> window

  說完了跟同一頁面中的iframe的數據交換,再來講說兩個窗口之間的數據交換。咱們知道用window.open()能夠打開一個新的窗口,而若是兩個窗口同源,則兩個窗口的通訊將會很是簡單,咱們能夠經過window.opener.functionName在新窗口中調用原來窗口的方法(和變量)。可是若是兩個窗口不一樣源,這樣的操做將會變得很艱難,幸運的是H5給咱們提供了postMessage,使得window.opener.postMessage()不會報錯!demo很簡單:

<!-- http://localhost:81/fish/index.html -->
<script type="text/javascript">
  // 打開一個新的窗口
  var popup = window.open('http://localhost:8080/index.html');

  /// When the popup has fully loaded, if not blocked by a popup blocker:
  setTimeout(function() {
      // 當前窗口向目標源傳數據
    popup.postMessage({"age":10}, 'http://localhost:8080');
  }, 1000);
</script>
<!-- http://localhost:8080/index.html -->
<script type="text/javascript"> 
  // 設置監聽,若是有數據傳過來,則打印
  window.addEventListener('message', function(e) {
    console.log(e);
    // console.log(e.source === window.opener);  // true
  });
</script>

  這裏要設置一個定時器的緣由是向目標窗口發送數據必須等目標窗口徹底加載完,也就是說要在目標窗口中先設置好「監聽器」,發送窗口發的數據才能被監聽到,因此給了個定時器delay,而由於加載時間的不肯定因此定時器的delay值也不能肯定;另一個可行的辦法是當目標頁面加載完後,發個消息個源頁面(postMessage),而源頁面收到消息,再用postMessage發送消息給目標頁面。

安全顧慮

  提到跨域交換數據,條件反射都會問一句,安全嗎?對於postMessage,答案是確定的。

  postMessage採用的是「雙向安全機制」。發送方發送數據的時候,會確認接受方的源(因此最好不要用*),而接受方監聽到message事件後,也能夠用event.origin判斷是否來自於正確可靠的發送方。

參考

  1. Window.postMessage()
  2. HTML5的postMessage使用記要
  3. Cross-window messaging with postMessage
  4. HTML5 postMessage 和 onmessage API 詳細應用
相關文章
相關標籤/搜索