postMessage可太有用了

慢思考快行動發表於2019-03-03

前言: 本篇文章我將帶大家一起來好好認識一下postMessage,包括它的相容性,對應的API介紹,以及常見的幾個使用場景,希望可以給有同樣困惑的盆友們一點啟發,給需要用這個技術的同僚們一些幫助.

postMessage的定義

postMessage是html5引入的API,postMessage()方法允許來自不同源的指令碼採用非同步方式進行有效的通訊,可以實現跨文字文件,多視窗,跨域訊息傳遞.多用於視窗間資料通訊,這也使它成為跨域通訊的一種有效的解決方案.

 postMessage的相容性

下圖是在caniuse上面搜到的postMessage相容性截圖,除IE瀏覽器的支援度比較低外,,其他瀏覽器的支援度良好.

postMessage可太有用了

 postMessage API介紹

傳送資料: 

otherWindow.postMessage(message, targetOrigin, [transfer]);
複製程式碼

otherWindow

視窗的一個引用,比如iframe的contentWindow屬性,執行window.open返回的視窗物件,或者是命名過的或數值索引的window.frames.

message

要傳送到其他視窗的資料,它將會被[!結構化克隆演算法](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm)序列化.這意味著你可以不受什麼限制的將資料物件安全的傳送給目標視窗而無需自己序列化.

targetOrigin

通過視窗的origin屬性來指定哪些視窗能接收到訊息事件,指定後只有對應origin下的視窗才可以接收到訊息,設定為萬用字元"*"表示可以傳送到任何視窗,但通常處於安全性考慮不建議這麼做.如果想要傳送到與當前視窗同源的視窗,可設定為"/"

transfer | 可選屬性

是一串和message同時傳遞的**Transferable**物件,這些物件的所有權將被轉移給訊息的接收方,而傳送一方將不再保有所有權.

接收資料: 監聽message事件的發生

window.addEventListener("message", receiveMessage, false) ;
function receiveMessage(event) {
     var origin= event.origin;
     console.log(event);
}複製程式碼

event物件的列印結果截圖如下:

postMessage可太有用了

這裡重點介紹event物件的四個屬性

  • data :   指的是從其他視窗傳送過來的訊息物件;
  • type:   指的是傳送訊息的型別;
  • source:   指的是傳送訊息的視窗物件;
  • origin:  指的是傳送訊息的視窗的源

postMessage的使用場景

場景一 跨域通訊(包括GET請求和POST請求)

 我們都知道JSONP可以實現解決GET請求的跨域問題,但是不能解決POST請求的跨域問題.而postMessage都可以.這裡只是列舉一個示例,僅供參考,具體的程式碼如何編寫要以具體的場景而定奧~

父窗體建立跨域iframe併傳送資訊

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>跨域POST訊息傳送</title>
        <script type="text/JavaScript">    
            // sendPost 通過postMessage實現跨域通訊將表單資訊傳送到 moweide.gitcafe.io上,
            // 並取得返回的資料    
            function sendPost() {        
                // 獲取id為otherPage的iframe視窗物件        
                var iframeWin = document.getElementById("otherPage").contentWindow;        
                // 向該視窗傳送訊息        
                iframeWin.postMessage(document.getElementById("message").value, 
                    'http://moweide.gitcafe.io');    
            }    
            // 監聽跨域請求的返回    
            window.addEventListener("message", function(event) {        
                console.log(event, event.data);    
            }, false);
        </script>
    </head>
    <body> 
        <textarea id="message"></textarea> 
        <input type="button" value="傳送" onclick="sendPost()"> 
        <iframe
            src="http://moweide.gitcafe.io/other-domain.html" id="otherPage"
            style="display:none"></iframe>
    </body>

</html>複製程式碼

子窗體接收資訊並處理

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>POST Handler</title>
        <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
        <script type="text/JavaScript">
            window.addEventListener("message", function( event ) {
                // 監聽父視窗傳送過來的資料向伺服器傳送post請求
                var data = event.data;
                $.ajax({
                    // 注意這裡的url只是一個示例.實際練習的時候你需要自己想辦法提供一個後臺介面
                    type: 'POST', 
                    url: 'http://moweide.gitcafe.io/getData',
                    data: "info=" + data,
                    dataType: "json"
                }).done(function(res){        
                    //將請求成功返回的資料通過postMessage傳送給父視窗        
                    window.parent.postMessage(res, "*");    
                }).fail(function(res){        
                    //將請求失敗返回的資料通過postMessage傳送給父視窗        
                    window.parent.postMessage(res, "*");    
                });
            }, false);
        </script>
    </head>

    <body></body>
</html>複製程式碼

場景二  WebWorker

JavaScript語言採用的是單執行緒模型,通常來說,所有任務都在一個執行緒上完成,一次只能做一件事,後面的任務要等到前面的任務被執行完成後才可以開始執行,但是這種方法如果遇到複雜費時的計算,就會導致發生阻塞,嚴重阻礙應用程式的正常執行.Web Worker為web內容在後臺執行緒中執行指令碼提供了一種簡單的方法,執行緒可以執行任務而不干擾使用者介面.一旦建立,一個worker可以將訊息傳送到建立它的JavaScript程式碼,通過訊息釋出到改程式碼指定的事件處理程式.

一個woker是使用一個建構函式建立一個物件,執行一個命名的JavaScript檔案-這個檔案將包含在工作執行緒中執行的程式碼,woker執行在另一個全域性上下文中,不同於當前的window,不能使用window來獲取全域性屬性.


一些侷限性

  • 只能載入同源指令碼檔案,不能直接操作DOM節點
  • Worker 執行緒不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 物件發出 AJAX 請求
  • 無法讀取本地檔案,只能載入網路檔案
  • 也不能使用window物件的預設方法和屬性,然而你可以使用大量window物件之下的東西,包括webSocket,indexedDB以及FireFoxOS專用的D阿塔Store API等資料儲存機制.檢視Functions and classes available to workers獲取詳情。

workers和主執行緒間的資料傳遞通過這樣的訊息機制進行——雙方都使用postMessage()方法傳送各自的訊息,使用onmessage事件處理函式來響應訊息(訊息被包含在Message事件的data屬性中)。這個過程中資料並不是被共享而是被複制;woker分為專用worker和共享worker,一個專用worker緊急能被首次生成它的指令碼使用,而共享woker可以同時被多個指令碼使用.

專用woker使用示例:

// main.js
if(window.Worker) {
    var myWorker = new Worker('http://xxx.com/worker.js');
    // 傳送訊息
    first.onchange = function() {
        myWorker.postMessage([first.value, second.value]);
        console.log("Message posted to worker");
    }
    second.onchange = function() {
      myWorker.postMessage([first.value,second.value]);
      console.log('Message posted to worker');
    }
    // 主執行緒 監聽onmessage以響應worker回傳的訊息
    myWorker.onmessage = function (e) {
      var textContent = e.data;
      console.log("message received from worker");  
    }
}

// worker.js

// 內建selfduixiang,,代表子執行緒本身, worker內部要載入其他指令碼,可通過importScripts()方法
onmessage = function(e) {
    console.log("message received from main script");
    var workerResult = "Result: " + (e.data[0] * e.data[1]);
    console.log("posting message\back to main script");
    postMessage(workerResult);
}複製程式碼

Web Worker的使用場景,用於收集埋點資料,可以用於大量複雜的資料計算,複雜的影象處理,大資料的處理.因為它不會阻礙主執行緒的正常執行和頁面UI的渲染.

埋點資料採集下的使用: 可在main.js中收集資料,將收集到的資訊通過postMessage的方式傳送給worker.js,在woker.js中進行相關運算和整理併傳送到伺服器端;當然,不使用Web Woker,通過在單頁面應用中的index.html中建立iframe也可以實現頁面間切換,頁面停留時長等資料的採集,具體的實現我就不細講了,感興趣的同學可在網上搜尋解決方案,有什麼疑問歡迎私信我~~~

場景三  Service Worker

可在瀏覽器控制檯的application中裡看到Service Worker的存在

postMessage可太有用了

Service Worker是web應用做離線儲存的一個最佳的解決方案,Service Worker和Web Worker的相同點是在常規的js引擎執行緒以外開闢了新的js執行緒去處理一些不適合在主執行緒上處理的業務,不同點主要包括以下幾點:

  • Web Worker式服務於特定頁面的,而Service Worker在被註冊安裝之後能夠在多個頁面使用
  • Service Worker常駐在瀏覽器中,不會因為頁面的關閉而被銷燬.本質上,它是一個後臺執行緒,只有你主動終結,或者瀏覽器回收,這個執行緒才會結束.
  • 生命週期,可呼叫的API也不同

我們可以使用Service Worker來進行快取,用js來攔截瀏覽器的http請求,並設定快取的檔案,從而建立離線web應用.關於Service Worker的概念的介紹就到這裡~~,感興趣的可以找相關文章學習,有疑問的歡迎私信與我探討~,這裡我們主要介紹的是使用postMessage方法進行Service Worker和頁面之間的通訊.

從頁面傳送資訊到Service Worker

需要注意一點,這個頁面如果直接扔進瀏覽器裡(使用的是file協議)開啟是會報錯的,要使用nginx做埠對映,或者用node搭建一個伺服器(使用http協議)來訪問該頁面(目前我猜測的原因是瀏覽器對file協議開啟的檔案做了一些服務的限制,如果有大佬知道具體原因還望告知).下文也將附上我的nginx做埠對映的配置.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Service Worker跨視窗通訊</title>
</head>
<body>
    <textarea id="showArea"></textarea>
    <script src="sw1.js"></script> 
    <script src="sw2.js"></script> 
    <script type="text/JavaScript">
        if('serviceWorker' in window.navigator) {
            // 對於多個不同scope的多個Service Worker,我們也可以給指定的Service Worker傳送訊息
            navigator.serviceWorker.register('./sw1.js', { scope:'./sw1'})
                .then(function(reg) {
                    console.log('success', reg);
                    return new Promise((resolve, reject) => {
                        const interval = setInterval(function() {
                            if(reg.active) {
                                clearInterval(interval);
                                resolve(reg.active);
                            }    
                        }, 1000);
                    }).then(sw => {
                        sw.postMessage("this message is from page to sw1");
                    })
                    
                })
            navigator.serviceWorker.register('./sw2.js', { scope:'./sw2'})
                .then(function(reg) {
                    console.log('success', reg);
                    return new Promise((resolve, reject) => {
                        const interval = setInterval(function() {
                            if(reg.active) {
                                clearInterval(interval);
                                resolve(reg.active);
                            }    
                        }, 1000);
                    }).then(sw => {
                        sw.postMessage("this message is from page to sw2");

                    })
                    
                });
                navigator.serviceWorker.addEventListener('message', function (event) {
                    console.log(event.data);
                    // 接受資料,並填充在 DOM 中
                    document.getElementById('showArea').value = event.data ;
                });
        }
    
    </script>
</body>
</html>

// sw1.js
self.addEventListener("message", function(event) {
    console.log("sw1.js " + event.data);
    event.source.postMessage('this message is from sw1.js, to page');
});

// sw2.js

self.addEventListener("message", function(event) {    
    console.log("sw2.js " + event.data); 
     // event.source是訊息來源頁面物件的引用   
    event.source.postMessage('this message is from sw2.js, to page');
});複製程式碼

nginx做埠對映的相關配置:

// nginx.conf
// 因為有多個專案會用到nginx服務做埠對映
// 所以我在nginx的目錄下新建了conf.d的檔案來存放每個專案的配置.
// 然後在主配置檔案裡通過include引入

 http {    
    # 這裡省略了一些你本機電腦上的nginx服務的配置    
    include conf.d/*.conf; 
}


// testHtml.conf
server {    
    listen 9090;    
    server_name       localhost;
    location / {        
        root  C:/Users/hzljie/Desktop/test/testb;  
        # 這是我的測試頁面的存放路徑,讀者用的時候記得根據自己的來更改奧        
        index  test.html test.htm;    
    }
}


複製程式碼

執行的效果的截圖:

postMessage可太有用了

這樣就實現了Service Worker 與其他頁面的通訊,感興趣的小夥伴可以一起來試一試奧.如果你出現報錯的情況,要仔細檢查自己的程式碼奧~

 結語

嗯,花了一番功夫終於總結完了,但是文章可能不夠詳盡,畢竟一個技術會有很多擴充套件和分支領域,很難一一介紹清楚,我寫部落格的水平也還有待提升,歡迎對這個有研究或者有興趣或者發現文章有錯誤的地方的夥伴們和我交流,共同進步~~~.我的郵箱2510909248@qq.com.


相關文章