前言: 本篇文章我將帶大家一起來好好認識一下postMessage,包括它的兼容性,對應的API介紹,以及常見的幾個使用場景,希望可以給有同樣困惑的盆友們一點啟發(fā),給需要用這個技術的同僚們一些幫助.
postMessage的定義
postMessage是html5引入的API,postMessage()方法允許來自不同源的腳本采用異步方式進行有效的通信,可以實現(xiàn)跨文本文檔,多窗口,跨域消息傳遞.多用于窗口間數(shù)據(jù)通信,這也使它成為跨域通信的一種有效的解決方案.
?postMessage的兼容性
下圖是在caniuse上面搜到的postMessage兼容性截圖,除IE瀏覽器的支持度比較低外,,其他瀏覽器的支持度良好.
?postMessage API介紹
發(fā)送數(shù)據(jù):?
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
窗口的一個引用,比如iframe的contentWindow屬性,執(zhí)行window.open返回的窗口對象,或者是命名過的或數(shù)值索引的window.frames.
message
要發(fā)送到其他窗口的數(shù)據(jù),它將會被[!結構化克隆算法](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm)序列化.這意味著你可以不受什么限制的將數(shù)據(jù)對象安全的傳送給目標窗口而無需自己序列化.
targetOrigin
通過窗口的origin屬性來指定哪些窗口能接收到消息事件,指定后只有對應origin下的窗口才可以接收到消息,設置為通配符"*"表示可以發(fā)送到任何窗口,但通常處于安全性考慮不建議這么做.如果想要發(fā)送到與當前窗口同源的窗口,可設置為"/"
transfer | 可選屬性
是一串和message同時傳遞的**Transferable**對象,這些對象的所有權將被轉移給消息的接收方,而發(fā)送一方將不再保有所有權.
接收數(shù)據(jù): 監(jiān)聽message事件的發(fā)生
window.addEventListener("message", receiveMessage, false) ;
function receiveMessage(event) {
? ? var origin= event.origin;
? ? console.log(event);
}
event對象的打印結果截圖如下:
這里重點介紹event對象的四個屬性
data :?指的是從其他窗口發(fā)送過來的消息對象;
type:?指的是發(fā)送消息的類型;
source:?指的是發(fā)送消息的窗口對象;
origin: ?指的是發(fā)送消息的窗口的源
postMessage的使用場景
場景一 跨域通信(包括GET請求和POST請求)
?我們都知道JSONP可以實現(xiàn)解決GET請求的跨域問題,但是不能解決POST請求的跨域問題.而postMessage都可以.這里只是列舉一個示例,僅供參考,具體的代碼如何編寫要以具體的場景而定奧~
父窗體創(chuàng)建跨域iframe并發(fā)送信息
<!DOCTYPE html>
<html>
? ? <head>
? ? ? ? <meta charset="utf-8">
? ? ? ? <meta http-equiv="X-UA-Compatible" content="IE=edge">
? ? ? ? <title>跨域POST消息發(fā)送</title>
? ? ? ? <script type="text/JavaScript">? ?
? ? ? ? ? ? // sendPost 通過postMessage實現(xiàn)跨域通信將表單信息發(fā)送到 moweide.gitcafe.io上,
? ? ? ? ? ? // 并取得返回的數(shù)據(jù)? ?
? ? ? ? ? ? function sendPost() {? ? ? ?
? ? ? ? ? ? ? ? // 獲取id為otherPage的iframe窗口對象? ? ? ?
? ? ? ? ? ? ? ? var iframeWin = document.getElementById("otherPage").contentWindow;? ? ? ?
? ? ? ? ? ? ? ? // 向該窗口發(fā)送消息? ? ? ?
? ? ? ? ? ? ? ? iframeWin.postMessage(document.getElementById("message").value, 'http://moweide.gitcafe.io');? ?
? ? ? ? ? ? }? ?
? ? ? ? ? ? // 監(jiān)聽跨域請求的返回? ?
? ? ? ? ? ? window.addEventListener("message", function(event) {? ? ? ?
? ? ? ? ? ? ? ? console.log(event, event.data);? ?
? ? ? ? ? ? }, false);
? ? ? ? </script>
? ? </head>
? ? <body>
? ? ? ? <textarea id="message"></textarea>
? ? ? ? <input type="button" value="發(fā)送" 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 ) {
? ? ? ? ? ? ? ? // 監(jiān)聽父窗口發(fā)送過來的數(shù)據(jù)向服務器發(fā)送post請求
? ? ? ? ? ? ? ? var data = event.data;
? ? ? ? ? ? ? ? $.ajax({
? ? ? ? ? ? ? ? ? ? // 注意這里的url只是一個示例.實際練習的時候你需要自己想辦法提供一個后臺接口
? ? ? ? ? ? ? ? ? ? type: 'POST',
? ? ? ? ? ? ? ? ? ? url: 'http://moweide.gitcafe.io/getData',
? ? ? ? ? ? ? ? ? ? data: "info=" + data,
? ? ? ? ? ? ? ? ? ? dataType: "json"
? ? ? ? ? ? ? ? }).done(function(res){? ? ? ?
? ? ? ? ? ? ? ? ? ? //將請求成功返回的數(shù)據(jù)通過postMessage發(fā)送給父窗口? ? ? ?
? ? ? ? ? ? ? ? ? ? window.parent.postMessage(res, "*");? ?
? ? ? ? ? ? ? ? }).fail(function(res){? ? ? ?
? ? ? ? ? ? ? ? ? ? //將請求失敗返回的數(shù)據(jù)通過postMessage發(fā)送給父窗口? ? ? ?
? ? ? ? ? ? ? ? ? ? window.parent.postMessage(res, "*");? ?
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }, false);
? ? ? ? </script>
? ? </head>
? ? <body></body>
</html>
場景二 ?WebWorker
JavaScript語言采用的是單線程模型,通常來說,所有任務都在一個線程上完成,一次只能做一件事,后面的任務要等到前面的任務被執(zhí)行完成后才可以開始執(zhí)行,但是這種方法如果遇到復雜費時的計算,就會導致發(fā)生阻塞,嚴重阻礙應用程序的正常運行.Web Worker為web內容在后臺線程中運行腳本提供了一種簡單的方法,線程可以執(zhí)行任務而不干擾用戶界面.一旦創(chuàng)建,一個worker可以將消息發(fā)送到創(chuàng)建它的JavaScript代碼,通過消息發(fā)布到改代碼指定的事件處理程序.
一個woker是使用一個構造函數(shù)創(chuàng)建一個對象,運行一個命名的JavaScript文件-這個文件將包含在工作線程中運行的代碼,woker運行在另一個全局上下文中,不同于當前的window,不能使用window來獲取全局屬性.
一些局限性
只能加載同源腳本文件,不能直接操作DOM節(jié)點
Worker 線程不能執(zhí)行alert()方法和confirm()方法睦焕,但可以使用 XMLHttpRequest 對象發(fā)出 AJAX 請求
無法讀取本地文件,只能加載網(wǎng)絡文件
也不能使用window對象的默認方法和屬性,然而你可以使用大量window對象之下的東西,包括webSocket,indexedDB以及FireFoxOS專用的D阿塔Store API等數(shù)據(jù)存儲機制.查看Functions and classes available to workers獲取詳情汇在。
workers和主線程間的數(shù)據(jù)傳遞通過這樣的消息機制進行——雙方都使用postMessage()方法發(fā)送各自的消息,使用onmessage事件處理函數(shù)來響應消息(消息被包含在Message事件的data屬性中)宇攻。這個過程中數(shù)據(jù)并不是被共享而是被復制;woker分為專用worker和共享worker,一個專用worker緊急能被首次生成它的腳本使用,而共享woker可以同時被多個腳本使用.
專用woker使用示例:
// main.js
if(window.Worker) {
? ? var myWorker = new Worker('http://xxx.com/worker.js');
? ? // 發(fā)送消息
? ? 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');
? ? }
? ? // 主線程 監(jiān)聽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的使用場景,用于收集埋點數(shù)據(jù),可以用于大量復雜的數(shù)據(jù)計算,復雜的圖像處理,大數(shù)據(jù)的處理.因為它不會阻礙主線程的正常執(zhí)行和頁面UI的渲染.
埋點數(shù)據(jù)采集下的使用: 可在main.js中收集數(shù)據(jù),將收集到的信息通過postMessage的方式發(fā)送給worker.js,在woker.js中進行相關運算和整理并發(fā)送到服務器端;當然,不使用Web Woker,通過在單頁面應用中的index.html中創(chuàng)建iframe也可以實現(xiàn)頁面間切換,頁面停留時長等數(shù)據(jù)的采集,具體的實現(xiàn)我就不細講了,感興趣的同學可在網(wǎng)上搜索解決方案,有什么疑問歡迎私信我~~~
場景三? Service Worker
可在瀏覽器控制臺的application中里看到Service Worker的存在
Service Worker是web應用做離線存儲的一個最佳的解決方案,Service Worker和Web Worker的相同點是在常規(guī)的js引擎線程以外開辟了新的js線程去處理一些不適合在主線程上處理的業(yè)務,不同點主要包括以下幾點:
Web Worker式服務于特定頁面的,而Service Worker在被注冊安裝之后能夠在多個頁面使用
Service Worker常駐在瀏覽器中,不會因為頁面的關閉而被銷毀.本質上,它是一個后臺線程,只有你主動終結,或者瀏覽器回收,這個線程才會結束.
生命周期,可調用的API也不同
我們可以使用Service Worker來進行緩存,用js來攔截瀏覽器的http請求,并設置緩存的文件,從而創(chuàng)建離線web應用.關于Service Worker的概念的介紹就到這里~~,感興趣的可以找相關文章學習,有疑問的歡迎私信與我探討~,這里我們主要介紹的是使用postMessage方法進行Service Worker和頁面之間的通訊.
從頁面發(fā)送信息到Service Worker
需要注意一點,這個頁面如果直接扔進瀏覽器里(使用的是file協(xié)議)打開是會報錯的,要使用nginx做端口映射,或者用node搭建一個服務器(使用http協(xié)議)來訪問該頁面(目前我猜測的原因是瀏覽器對file協(xié)議打開的文件做了一些服務的限制,如果有大佬知道具體原因還望告知).下文也將附上我的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發(fā)送消息
? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? ? ? // 接受數(shù)據(jù)尖淘,并填充在 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;?
? ? ? ? # 這是我的測試頁面的存放路徑,讀者用的時候記得根據(jù)自己的來更改奧? ? ? ?
? ? ? ? index? test.html test.htm;? ?
? ? }
}
運行的效果的截圖:
這樣就實現(xiàn)了Service Worker 與其他頁面的通信,感興趣的小伙伴可以一起來試一試奧.如果你出現(xiàn)報錯的情況,要仔細檢查自己的代碼奧~
?結語
嗯,花了一番功夫終于總結完了,但是文章可能不夠詳盡,畢竟一個技術會有很多擴展和分支領域,很難一一介紹清楚,我寫博客的水平也還有待提升,歡迎對這個有研究或者有興趣或者發(fā)現(xiàn)文章有錯誤的地方的伙伴們和我交流,共同進步~~~.我的郵箱2510909248@qq.com.