1 H5 postMessage 和 onmessage
1.1 引言
隨著 HTML5
的發(fā)展于樟,了解并熟悉 HTML5
的 API
接口是非常重要的梯码。postMessage(send) 和 onmessage
此組 API
在 HTML5
中有著廣泛的應用两疚,比如 Web Workers
中應用此組 API
實現(xiàn)多個線程間 JavaScript
調(diào)用功能 ,Cross-document messaging
中實現(xiàn)兩個不同域間 JavaScript
調(diào)用功能等等惋耙。本文主要介紹此組 API
在 Web Workers
斯撮,Cross-document messaging
台妆,WebSockets
以及 Server-Sent Events
中的詳細應用情況沙廉。
1.2 Web Workers
1.2.1 Web Workers簡介
至 2008 年 W3C
制定出第一個 HTML5
草案開始畴博,HTML5
承載了越來越多嶄新的特性和功能。它不但強化了 Web
系統(tǒng)或網(wǎng)頁的表現(xiàn)性能蓝仲,而且還增加了對本地數(shù)據(jù)庫等 Web
應用功能的支持俱病。其中,最重要的一個便是對多線程的支持袱结。在 HTML5
中提出了工作線程(Web Workers
)的概念亮隙,并且規(guī)范出 Web Workers
的三大主要特征:能夠長時間運行(響應),理想的啟動性能以及理想的內(nèi)存消耗垢夹。Web Workers
允許開發(fā)人員編寫能夠長時間運行而不被用戶所中斷的后臺程序溢吻,去執(zhí)行事務或者邏輯,并同時保證頁面對用戶的及時響應果元。
Web Workers
為 Web
前端網(wǎng)頁上的腳本提供了一種能在后臺進程中運行的方法促王。一旦它被創(chuàng)建,Web Workers
就可以通過 postMessage
向任務池發(fā)送任務請求而晒,執(zhí)行完之后再通過 postMessage
返回消息給創(chuàng)建者指定的事件處理程序 ( 通過 onmessage
進行捕獲 )蝇狼。Web Workers
進程能夠在不影響用戶界面的情況下處理任務,并且倡怎,它還可以使用 XMLHttpRequest
來處理I/O
迅耘,但通常,后臺進程(包括 Web Workers
進程)不能對 DOM
進行操作监署。如果希望后臺程序處理的結果能夠改變 DOM
颤专,只能通過返回消息給創(chuàng)建者的回調(diào)函數(shù)進行處理。
瀏覽器對 HTML5
支持情況可以參考網(wǎng)站 https://caniuse.com/
1.2.2 在Web Workers中使用 postMessage 和 onmessage
首先钠乏,需要在客戶端頁面的 JavaScript
代碼中 new
一個 Worker
實例出來栖秕,參數(shù)是需要在另一個線程中運行的 JavaScript
文件名稱。然后在這個實例上監(jiān)聽 onmessage
事件晓避。最后另一個線程中的 JavaScript
就可以通過調(diào)用 postMessage
方法在這兩個線程間傳遞數(shù)據(jù)了
主線程中創(chuàng)建 Worker
實例簇捍,并監(jiān)聽 onmessage
事件
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test Web worker</title>
<script type="text/JavaScript">
function init(){
var worker = new Worker('compute.js');
//event參數(shù)中有data屬性,就是子線程中返回的結果數(shù)據(jù)
worker.onmessage= function (event) {
// 把子線程返回的結果添加到 div 上
document.getElementById("result").innerHTML +=
event.data+"<br/>";
};
}
</script>
</head>
<body onload="init()">
<div id="result"></div>
</body>
</html>
在客戶端的 compute.js
中够滑,只是簡單的重復多次加和操作垦写,最后通過 postMessage
方法把結果返回給主線程,目的就是等待一段時間彰触。而在這段時間內(nèi)梯投,主線程不應該被阻塞,用戶可以通過拖拽瀏覽器,變大縮小瀏覽器窗口等操作測試這一現(xiàn)象分蓖。這個非阻塞主線程的結果就是 Web Workers
想達到的目的尔艇。
compute.js
中調(diào)用 postMessage
方法返回計算結果
var i=0;
function timedCount(){
for(var j=0,sum=0;j<100;j++){
for(var i=0;i<100000000;i++){
sum+=i;
}
}
// 調(diào)用 postMessage 向主線程發(fā)送消息
postMessage(sum);
}
postMessage("Before computing,"+new Date());
timedCount();
postMessage("After computing,"+new Date());
1.3 Cross-document messaging
1.3.1 Cross-document messaging 簡介
由于同源策略的限制,JavaScript
跨域的問題么鹤,一直是一個頗為棘手的問題终娃。HTML5
提供了在網(wǎng)頁文檔之間互相接收與發(fā)送信息的功能。使用這個功能蒸甜,只要獲取到網(wǎng)頁所在窗口對象的實例棠耕,不僅同源(域 + 端口號)的 Web
網(wǎng)頁之間可以互相通信,甚至可以實現(xiàn)跨域通信柠新。 要想接收從其他窗口發(fā)送來的信息窍荧,必須對窗口對象的 onmessage
事件進行監(jiān)聽,其它窗口可以通過 postMessage
方法來傳遞數(shù)據(jù)恨憎。該方法使用兩個參數(shù):第一個參數(shù)為所發(fā)送的消息文本蕊退,但也可以是任何 JavaScript
對象(通過 JSON
轉換對象為文本),第二個參數(shù)為接收消息的對象窗口的 URL
地址憔恳,可以在 URL
地址字符串中使用通配符*
指定全部地瓤荔。
1.3.2 在Cross-document messaging中使用 postMessage 和 onmessage
為了實現(xiàn)不同域之間的通信,需要在操作系統(tǒng)的 hosts
文件添加兩個域名钥组,進行模擬输硝。
hosts
文件中添加兩個不同的域名
127.0.0.1 parent.com
127.0.0.1 child.com
在父網(wǎng)頁中通過 iframe
嵌入子頁面,并在 JavaScript
代碼中調(diào)用 postMessage
方法發(fā)送數(shù)據(jù)到子窗口者铜。
父頁面中嵌入子頁面腔丧,調(diào)用 postMessage
方法發(fā)送數(shù)據(jù)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test Cross-domain communication using HTML5</title>
<script type="text/JavaScript">
function sendIt(){
// 通過 postMessage 向子窗口發(fā)送數(shù)據(jù)
document.getElementById("otherPage").contentWindow
.postMessage(
document.getElementById("message").value,
"http://child.com:8080"
);
}
</script>
</head>
<body>
<!-- 通過 iframe 嵌入子頁面 -->
<iframe src="http://child.com:8080/TestHTML5/other-domain.html"
id="otherPage"></iframe>
<br/><br/>
<input type="text" id="message"><input type="button"
value="Send to child.com" onclick="sendIt()" />
</body>
</html>
在子窗口中監(jiān)聽 onmessage
事件,并用 JavaScript
實現(xiàn)顯示父窗口發(fā)送過來的數(shù)據(jù)作烟。
子窗口中監(jiān)聽 onmessage
事件,顯示父窗口發(fā)送來的數(shù)據(jù)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Web page from child.com</title>
<script type="text/JavaScript">
//event 參數(shù)中有 data 屬性砾医,就是父窗口發(fā)送過來的數(shù)據(jù)
window.addEventListener("message", function( event ) {
// 把父窗口發(fā)送過來的數(shù)據(jù)顯示在子窗口中
document.getElementById("content").innerHTML+=event.data+"<br/>";
}, false );
</script>
</head>
<body>
Web page from http://child.com:8080
<div id="content"></div>
</body>
</html>
父窗口嵌入子窗口
父窗口發(fā)送數(shù)據(jù)到子窗口
1.4 WebSockets
1.4.1 WebSockets 簡介
在 Web
應用中拿撩,HTTP
協(xié)議決定了客戶端和服務端連接是短連接,即客戶端 Request
如蚜,服務端 Response
压恒,連接斷開。要想實現(xiàn)客戶端和服務端實時通信错邦,只能通過客戶端輪詢來實現(xiàn)探赫。服務端推送數(shù)據(jù)也并不是字面上意思上的直接推,其實還是客戶端自己取撬呢。WebSockets
是 HTML5
規(guī)范新引入的功能伦吠,用于解決瀏覽器與后臺服務器雙向通訊的問題,使用 WebSockets
技術,后臺可以隨時向前端推送消息毛仪,以保證前后臺狀態(tài)統(tǒng)一搁嗓。
1.4.2 在WebSockets中使用 send 和 onmessage
由于主要介紹 postMessage(send)
和 onmessage
客戶端 API 的應用,而 WebSockets
涉及到服務器端代碼的實現(xiàn)箱靴,所以本文將選取最簡單的服務器端框架來編寫服務器代碼腺逛。WebSockets
服務器端有 jetty
提供的基于 Java
的實現(xiàn),有 WebSocket-Node
基于 node.js
的實現(xiàn)衡怀,在 .Net 4.5
中也直接提供了 WebSockets
的支持棍矛。本文將使用 WebSocket-Node
提供的示例代碼,稍作修改作為 WebSockets
的服務器端
首先抛杨,需要在客戶端通過 JavaScript
代碼 new
一個 WebSocket
實例出來茄靠,參數(shù)是實現(xiàn) WebSocket
服務器端 URL
地址。然后在這個實例上監(jiān)聽 onmessage
事件接收服務器端發(fā)送過來的數(shù)據(jù)蝶桶。當然慨绳,客戶端也可以調(diào)用 send
方法,發(fā)送數(shù)據(jù)到服務器端真竖。
創(chuàng)建 WebSocket
對象脐雪,并監(jiān)聽 onmessage
事件
connect : function() {
var location ="ws://localhost:8000/";
// 創(chuàng)建 WebSockets 并傳入 WebSockets server 地址
this._ws =new WebSocket(location);
this._ws.onmessage=this._onmessage;
//WebSockets 還提供了 onopen 以及 onclose 事件
this._ws.onopen =this._onopen;
this._ws.onclose =this._onclose;
}
在_onmessage
方法中,接收數(shù)據(jù)恢共,并顯示在頁面上
_onmessage
方法
_onmessage : function(event) {
//event 參數(shù)中有 data 屬性战秋,就是服務器發(fā)送過來的數(shù)據(jù)
if (event.data) {
var messageBox = document.getElementById('messageBox');
var spanText = document.createElement('span');
spanText.className ='text';
// 把服務器發(fā)送過來的數(shù)據(jù)顯示在窗口中
spanText.innerHTML = event.data;
var lineBreak = document.createElement('br');
messageBox.appendChild(spanText);
messageBox.appendChild(lineBreak);
messageBox.scrollTop = messageBox.scrollHeight
- messageBox.clientHeight;
}
},
在 _onopen
方法中,調(diào)用 _send
方法發(fā)送一條消息到服務器端讨韭,告之連接已經(jīng)建立脂信。在_onclose
方法中,把 WebSocket
的實例設置成 null
透硝,釋放資源狰闪。
_onopen,_onclose 以及 send
方法
_onopen : function() {
server._send("Client:Open WebSockets,"+new Date());
},
//message 參數(shù)就是客戶端向服務器端發(fā)送的數(shù)據(jù)
_send : function(message) {
if (this._ws)
this._ws.send(message);
},
// 此方法提供外部代碼調(diào)用
send : function(text) {
if (text !=null&& text.length >0)
server._send(text);
},
_onclose : function(m) {
this._ws =null;
}
把這些方法封裝在一個 server
對象中濒生,方便提供外部調(diào)用埋泵。用戶只需要先調(diào)用 server
的 connect
方法建立連接,然后調(diào)用 send
方法發(fā)送數(shù)據(jù)罪治。
封裝客戶端實現(xiàn)
var server = {
// 對外主要提供 connect 和 send 方法
connect : function() {...},
_onopen : function() {...},
_send : function(message) {...},
send : function(text) {...},
_onmessage : function(event) {...},
_onclose : function(m) {...}
};
在服務器端丽声,通過 JavaScript
語言簡單修改 WebSocket-Node
中提供的 echo-server.js
示例即可
WebSockets
服務器端簡單實現(xiàn)
// 監(jiān)聽客戶端的連接請求
wsServer.on('connect', function(connection) {
function sendCallback(err) {
if (err) console.error("send() error: " + err);
}
// 監(jiān)聽客戶端發(fā)送數(shù)據(jù)的請求
connection.on('message', function(message) {
if (message.type === 'utf8') {// 區(qū)別客戶端發(fā)過來的數(shù)據(jù)是文本還是二進制類型
connection.sendUTF(
"Server:Get message:<br/>"+message.utf8Data, sendCallback
);
}
else if (message.type === 'binary') {
connection.sendBytes(message.binaryData, sendCallback);
}
});
connection.on('close', function(reasonCode, description) {
});
});
1.5 Server-Sent Events
1.5.1 Server-Sent Events 簡介
HTML5 Server-Sent
事件模型允許您從服務器 push
實時數(shù)據(jù)到瀏覽器。利用 Eventsource
對象處理與頁面間的接收和發(fā)送數(shù)據(jù)觉义。在客戶端雁社,我們使用 HTML5+JavaScript
,服務端使用 Java
晒骇。在現(xiàn)存的 Ajax
模式中霉撵,web
頁面會持續(xù)不斷地請求服務器傳輸新數(shù)據(jù)磺浙,由客戶端負責請求數(shù)據(jù)。而在服務端發(fā)送模式下喊巍,無需在客戶端代碼中執(zhí)行連續(xù)的數(shù)據(jù)請求屠缭,而是由服務端 push
推送更新。一旦在頁面中初始化了 Server-Sent
事件崭参,服務端腳本將持續(xù)地發(fā)送更新呵曹。客戶端 JavaScript
代碼一旦接收到更新就將新的數(shù)據(jù)寫入頁面中展示出來何暮。
1.5.2 在 Server-Sent Events 中使用 onmessage
Server-Sent Events
和 WebSockets
有相同之處奄喂,WebSockets
實現(xiàn)了服務器端以及客戶端的雙向通信功能,而 Server-Sent Events
則僅是指服務器端到客戶端的單向通信海洼,而且 Server-Sent Events
同樣需要服務器端的實現(xiàn)跨新,本文將使用基于 Java
的 Servlet
技術實現(xiàn)服務器端
首先,在客戶端通過 JavaScript
代碼 new
一個 EventSource
實例出來坏逢,參數(shù)是實現(xiàn) EventSource
服務器端URL
地址域帐。然后在這個實例上監(jiān)聽 onmessage
事件接收服務器端發(fā)送過來的數(shù)據(jù)。
創(chuàng)建 EventSource
對象是整,并監(jiān)聽 onmessage
事件
if (!!window.EventSource) {
// 創(chuàng)建 EventSource 實例肖揣,傳入 server 地址
var source = new EventSource('/TestHTML5/ServerSentEvent');
} else {
console.log("Your browser doesn't support server-sent event");
}
// 監(jiān)聽 message 事件,等待接收服務器端發(fā)送過來的數(shù)據(jù)
source.addEventListener('message', function(event) {
//event 參數(shù)中有 data 屬性浮入,就是服務器發(fā)送過來的數(shù)據(jù)
console.log(event.data);
}, false);
//EventSource 還提供了 onopen 以及 onerror 事件
source.addEventListener('open', function(event) {
}, false);
source.addEventListener('error', function(event) {
if (event.readyState == EventSource.CLOSED) {
}
}, false);
服務器端龙优,在 Java
語言實現(xiàn)的 Servlet doGet
方法中使用 response
對象向客戶端寫數(shù)據(jù)
服務器端簡單實現(xiàn)
// 這里必須設置 Content-Type 為 text/event-stream
response.setHeader("Content-Type", "text/event-stream");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding ("UTF-8");
String id = new Date().toString();
response.getWriter().println("id:"+id);
// 向客戶端寫兩行數(shù)據(jù)
response.getWriter().println("data:server-sent event is working.");
response.getWriter().println("data:test server-sent event multi-line data");
response.getWriter().println();
response.getWriter().flush();