1. 程式人生 > >用postMessage解決跨域通訊和跨域呼叫js問題

用postMessage解決跨域通訊和跨域呼叫js問題

本文轉自:https://bbs.implug.cn/?thread-3.htm
平時做web開發的時候關於訊息傳遞,除了客戶端與伺服器傳值,還有幾個經常會遇到的問題:

  • 多視窗之間訊息傳遞(newWin = window.open(…));
  • 頁面與巢狀的iframe訊息傳遞。
  • 同域名下可以通過parent呼叫父級頁面的js跨域時如何呼叫。

這裡介紹的方法是用運用postMessage方式解決
發訊息方呼叫postMessage方法傳送訊息 postMessage是html5引入的API可以更方便、有效、安全的解決這些問題。postMessage()方法允許來自不同源的指令碼採用非同步方式進行有限的通訊,可以實現跨文字檔、多視窗、跨域訊息傳遞。
收訊息方用MessageEvent監聽訊息。

postMessage(data,origin)方法接受兩個引數

  • data:要傳遞的資料, html5規範中提到該引數可以是JavaScript的任意基本型別或可複製的物件,然而並不是所有瀏覽器都做到了這點兒,部分瀏覽器只能處理字串引數,所以我們在傳遞引數的時候需要使用JSON.stringify()方法對物件引數序列化,在低版本IE中引用json2.js可以實現類似效果。
  • origin:字串引數,指明目標視窗的源, 協議+主機+埠號[+URL],URL會被忽略,所以可以不寫,這個引數是為了安全考慮,someWindow.postMessage()方法只會在someWindow所在的源(url的protocol, host, port)和指定源一致時才會成功觸發message
  • event,當然如果願意也可以將引數設定為"*",someWindow可以在任意源,如果要指定和當前視窗同源的話設定為"/"。

MessageEvent的屬性

  • data:顧名思義,是傳遞來的message
  • source:傳送訊息的視窗物件
  • origin:傳送訊息視窗的源(協議+主機+埠號)。

同域父子頁面間通訊

程式碼示例:

父頁面a.html:

    // localhost:9011/a.html
    <h1 class="header">page A</h1>
    <div class="mb20">
        <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
        <button style="font-size:20px;" onclick="send()">post message</button>
    </div>
    <!-- 不跨域的情況 -->
    <iframe src="b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>

    <script>
    function send() {//傳送訊息
        var data = document.querySelector('#data').value;
        // 注意: 只會觸發當前window物件的message事件
        // 也可以訪問子頁面的window物件,觸發子頁面的message事件 (window.frames[0].postMessage(...))
        // window.postMessage(data, '/'); 
       // data = {name: 'sandy', age: 20, fav: {sing: true, shop: false}}; // 也可以傳普通物件
        window.frames[0].postMessage(data, '/'); // 觸發同域子頁面的message事件(注意這裡用的window.frames[0]代表傳送訊息給frames頁面)
        //window.frames[0].postMessage(data, 'http://localhost:9022/'); // 觸發跨域子頁面的messag事件
    }

    // 當前頁面執行 window.postMessage(..)或子頁面執行 parent.postMessage(...) 都會觸發下面的回撥, messageEvent.source不同而已
    window.addEventListener('message', function(messageEvent) {
        var data = messageEvent.data;// messageEvent: {source, currentTarget, data}
        console.info('message from child:', data);
    }, false);
    </script>

子頁面b.html

    //> localhost:9011/b.html
    <h1 class="header">page B</h1>
    <input type="text" id="inp" value="some contents..">
    <button onclick="send()">send</button>

    <script>
    window.addEventListener('message', function(ev) {//監聽接收訊息 ev包含上面寫道MessageEvent的三個屬性
        if (ev.source !== window.parent) {return;}//這裡通過判斷訊息來源來進行安全驗證
        var data = ev.data;
        console.info('message from parent:', data);
    }, false);

    function send() { 
        var data = document.querySelector('#inp').value;
        //window.postMessage(data, '*'); // 傳送訊息觸發當前頁面的message事件(注意這裡用的window代表傳送給子頁面)
        parent.postMessage(data, '*'); // 傳送訊息觸發父頁面的message事件(注意加上parent代表傳送給父頁面)
        //parent.postMessage(data,'http://localhost:9011/');//若父頁面的域名和指定的不一致,則postMessage失敗
    }
    </script>

跨域父子頁面間通訊

父頁面a.html:

   //> localhost:9011/a.html
   <h1 class="header">page A</h1>
   <div class="mb20">
       <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
       <button style="font-size:20px;" onclick="send()">post message</button>
   </div>
   <!-- 跨域的情況 -->
   <iframe src="http://localhost:9022/b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>

   <script>
   function send() {
       var data = document.querySelector('#data').value;

       window.frames[0].postMessage(data, 'http://localhost:9022/'); // 觸發跨域子頁面的messag事件(window.frames[0]代表傳送訊息給frames[0]頁面),這裡的第二個引數表示可以接受此訊息的頁面的視窗的源origin如果天*帶個所有。
   }

   window.addEventListener('message', function(messageEvent) {
       //接收訊息的監聽,這裡可以通過ev.origin判斷是否是合法的來源
       var data = messageEvent.data; 
       console.info('message from child:', data);
   }, false);
   </script>

子頁面b.html

    //> localhost:9022/b.html(兩個頁面埠號不同.為跨域的一種)
    <h1 class="header">page B</h1>
    <input type="text" id="inp" value="some contents..">
    <button onclick="send()">send</button>

    <script>
    window.addEventListener('message', function(ev) {
    //接收訊息的監聽,這裡可以通過ev.origin判斷是否是合法的來源
        var data = ev.data;
        console.info('message from parent:', data);
    }, false);

    function send() {
        var data = document.querySelector('#inp').value;
        parent.postMessage(data, 'http://localhost:9011/'); //若父頁面的域名和指定的不一致,則postMessage失敗(parent代表傳送訊息給frames[0]頁面)
        // parent.postMessage(data, '*'); // 觸發父頁面的message事件*代表任何頁面都可接收,
    }
    </script>

通過上面的兩個例子我們已經解決了,傳遞訊息和跨域傳遞訊息,那麼怎麼跨域呼叫JS呢。 原理很簡單,就是把需要在另一個頁面執行的js程式碼寫成字串用postMessage方法傳遞過去,然後另一個頁面接收到後用eval()直接執行這段程式碼。

示例跨域情況下子頁面讓父頁面alert(“hello!”);:

父頁面a.html:

 //> localhost:9011/a.html
 <h1 class="header">page A</h1>
 <div class="mb20">
     <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
 </div>
 <!-- 跨域的情況 -->
 <iframe src="http://localhost:9022/b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>

 <script>
 window.addEventListener('message', function(messageEvent) {
     //接收訊息的監聽,這裡可以通過ev.origin判斷是否是合法的來源
     if(ev.origin='http://localhost:9022'){
         return;
     }
     eval(ev.data);
 }, false);
 </script>

子頁面b.html

    //> localhost:9022/b.html(兩個頁面埠號不同.為跨域的一種)
    <h1 class="header">page B</h1>
    <input type="text" id="inp" value="some contents..">
    <button onclick="send()">send</button>

    <script>

    function send() {
        var data = 'alert("hello!");';
        parent.postMessage(data, '*'); // 觸發父頁面的message事件*代表任何頁面都可接收,
    }
    </script>

本文轉自:https://bbs.implug.cn/?thread-3.htm