postMessage可太有用了

前言: 本篇文章我將帶你們一塊兒來好好認識一下postMessage,包括它的兼容性,對應的API介紹,以及常見的幾個使用場景,但願能夠給有一樣困惑的盆友們一點啓發,給須要用這個技術的同僚們一些幫助.html

postMessage的定義

postMessage是html5引入的API,postMessage()方法容許來自不一樣源的腳本採用異步方式進行有效的通訊,能夠實現跨文本文檔,多窗口,跨域消息傳遞.多用於窗口間數據通訊,這也使它成爲跨域通訊的一種有效的解決方案.vue

 postMessage的兼容性

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


 postMessage API介紹

發送數據: 

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

otherWindow

窗口的一個引用,好比iframe的contentWindow屬性,執行window.open返回的窗口對象,或者是命名過的或數值索引的window.frames.node

message

要發送到其餘窗口的數據,它將會被[!結構化克隆算法](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm)序列化.這意味着你能夠不受什麼限制的將數據對象安全的傳送給目標窗口而無需本身序列化.jquery

targetOrigin

經過窗口的origin屬性來指定哪些窗口能接收到消息事件,指定後只有對應origin下的窗口才能夠接收到消息,設置爲通配符"*"表示能夠發送到任何窗口,但一般處於安全性考慮不建議這麼作.若是想要發送到與當前窗口同源的窗口,可設置爲"/"nginx

transfer | 可選屬性

是一串和message同時傳遞的**Transferable**對象,這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權.git

接收數據: 監聽message事件的發生

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

event對象的打印結果截圖以下:web


這裏重點介紹event對象的四個屬性ajax

  • 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的存在


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;    
    }
}


複製代碼

運行的效果的截圖:


這樣就實現了Service Worker 與其餘頁面的通訊,感興趣的小夥伴能夠一塊兒來試一試奧.若是你出現報錯的狀況,要仔細檢查本身的代碼奧~

 結語

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

往期文章

 在vue中使用SockJS實現webSocket通訊

手把手教你製做表格表頭懸浮(table-header-fixed)

Expires, Last-Modified, Etag緩存機制

相關文章
相關標籤/搜索