postMessage可太有用了


前言: 本篇文章我將帶大家一起來好好認識一下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.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末奕锌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子德澈,更是在濱河造成了極大的恐慌歇攻,老刑警劉巖固惯,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梆造,死亡現(xiàn)場離奇詭異,居然都是意外死亡葬毫,警方通過查閱死者的電腦和手機镇辉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贴捡,“玉大人忽肛,你說我怎么就攤上這事±谜” “怎么了屹逛?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汛骂。 經(jīng)常有香客問我罕模,道長,這世上最難降的妖魔是什么帘瞭? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任淑掌,我火速辦了婚禮,結果婚禮上蝶念,老公的妹妹穿的比我還像新娘抛腕。我一直安慰自己芋绸,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布担敌。 她就那樣靜靜地躺著摔敛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪全封。 梳的紋絲不亂的頭發(fā)上舷夺,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音售貌,去河邊找鬼给猾。 笑死,一個胖子當著我的面吹牛颂跨,可吹牛的內容都是我干的敢伸。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼恒削,長吁一口氣:“原來是場噩夢啊……” “哼池颈!你這毒婦竟也來了?” 一聲冷哼從身側響起钓丰,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤躯砰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后携丁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琢歇,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年梦鉴,在試婚紗的時候發(fā)現(xiàn)自己被綠了李茫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡肥橙,死狀恐怖贯要,靈堂內的尸體忽然破棺而出奸腺,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布俭厚,位于F島的核電站类早,受9級特大地震影響蔼啦,放射性物質發(fā)生泄漏寝殴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一藕溅、第九天 我趴在偏房一處隱蔽的房頂上張望匕得。 院中可真熱鬧,春花似錦、人聲如沸汁掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽考阱。三九已至翠忠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乞榨,已是汗流浹背秽之。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吃既,地道東北人考榨。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像鹦倚,于是被迫代替她去往敵國和親河质。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361