postMessage可太有用了


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

postMessage的定義

postMessage是html5引入的API,postMessage()方法允許來自不同源的腳本采用異步方式進行有效的通信,可以實現跨文本文檔,多窗口,跨域消息傳遞.多用于窗口間數據通信,這也使它成為跨域通信的一種有效的解決方案.

?postMessage的兼容性

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

?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對象的打印結果截圖如下:

這里重點介紹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="http://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.postMessage('this message is from sw2.js, to page');? // event.source是消息來源頁面對象的引用});

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.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。