HTML5 postMessage 和 onmessage API 詳細應用


隨着 HTML5 的發展,了解並熟悉 HTML5 的 API 接口是非常重要的。postMessage(send) 和 onmessage 此組 API 在 HTML5 中有着廣泛的應用,比如 Web Workers 中應用此組 API 實現多個線程間 JavaScript 調用功能 ,Cross-document messaging 中實現兩個不同域間 JavaScript 調用功能等等。本文主要介紹此組 API 在 Web Workers,Cross-document messaging,WebSockets 以及 Server-Sent Events 中的詳細應用情況

至 2008 年 W3C 制定出第一個 HTML5 草案開始,HTML5 承載了越來越多嶄新的特性和功能。它不但強化了 Web 系統或網頁的表現性能,而且還增加了對本地數據庫等 Web 應用功能的支持。其中,最重要的一個便是對多線程的支持。在 HTML5 中提出了工作線程(Web Workers)的概念,並且規范出 Web Workers 的三大主要特征:能夠長時間運行(響應),理想的啟動性能以及理想的內存消耗。Web Workers 允許開發人員編寫能夠長時間運行而不被用戶所中斷的后台程序,去執行事務或者邏輯,並同時保證頁面對用戶的及時響應。

 

Cookie腫么了

 

cookie的缺陷是非常明顯的

1. 數據大小:作為存儲容器,cookie的大小限制在4KB左右這是非常坑爹的,尤其對於現在復雜的業務邏輯需求,4KB的容量除了存儲一些配置字段還簡單單值信息,對於絕大部分開發者來說真的不知指望什么了。

2. 安全性問題:由於在HTTP請求中的cookie是明文傳遞的(HTTPS不是),帶來的安全性問題還是很大的。

3. 網絡負擔:我們知道cookie會被附加在每個HTTP請求中,在HttpRequest 和HttpResponse的header中都是要被傳輸的,所以無形中增加了一些不必要的流量損失。

 

Web Workers

Web Workers 簡介

Web Workers 為 Web 前端網頁上的腳本提供了一種能在后台進程中運行的方法。一旦它被創建,Web Workers 就可以通過 postMessage 向任務池發送任務請求,執行完之后再通過 postMessage 返回消息給創建者指定的事件處理程序 ( 通過 onmessage 進行捕獲 )。Web Workers 進程能夠在不影響用戶界面的情況下處理任務,並且,它還可以使用 XMLHttpRequest 來處理 I/O,但通常,后台進程(包括 Web Workers 進程)不能對 DOM 進行操作。如果希望后台程序處理的結果能夠改變 DOM,只能通過返回消息給創建者的回調函數進行處理。

瀏覽器對 HTML5 支持情況可以參考網站 When can I use...

在 Web Workers 中使用 postMessage 和 onmessage

首先,需要在客戶端頁面的 JavaScript 代碼中 new 一個 Worker 實例出來,參數是需要在另一個線程中運行的 JavaScript 文件名稱。然后在這個實例上監聽 onmessage 事件。最后另一個線程中的 JavaScript 就可以通過調用 postMessage 方法在這兩個線程間傳遞數據了。

清單 1. 主線程中創建 Worker 實例,並監聽 onmessage 事件
 <html> 
 <head> 
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> 
 <title>Test Web worker</title> 
 <script type="text/JavaScript"> 
     function init(){ 
         var worker = new Worker('compute.js'); 
         //event 參數中有 data 屬性,就是子線程中返回的結果數據
         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 方法把結果返回給主線程,目的就是等待一段時間。而在這段時間內,主線程不應該被阻塞,用戶可以通過拖拽瀏覽器,變大縮小瀏覽器窗口等操作測試這一現象。這個非阻塞主線程的結果就是 Web Workers 想達到的目的。

清單 2. compute.js 中調用 postMessage 方法返回計算結果
var i=0; 

 function timedCount(){ 
     for(var j=0,sum=0;j<100;j++){ 
         for(var i=0;i<100000000;i++){ 
             sum+=i; 
         } 
     } 
     // 調用 postMessage 向主線程發送消息
     postMessage(sum); 
 } 

 postMessage("Before computing,"+new Date()); 
 timedCount(); 
 postMessage("After computing,"+new Date());

 

圖 1. 瀏覽器中運行結果

瀏覽器中運行結果

Cross-document messaging

Cross-document messaging 簡介

由於同源策略的限制,JavaScript 跨域的問題,一直是一個頗為棘手的問題。HTML5 提供了在網頁文檔之間互相接收與發送信息的功能。使用這個功能,只要獲取到網頁所在窗口對象的實例,不僅同源(域 + 端口號)的 Web 網頁之間可以互相通信,甚至可以實現跨域通信。 要想接收從其他窗口發送來的信息,必須對窗口對象的 onmessage 事件進行監聽,其它窗口可以通過 postMessage 方法來傳遞數據。該方法使用兩個參數:第一個參數為所發送的消息文本,但也可以是任何 JavaScript 對象(通過 JSON 轉換對象為文本),第二個參數為接收消息的對象窗口的 URL 地址,可以在 URL 地址字符串中使用通配符'*'指定全部地。

在 Cross-document messaging 中使用 postMessage 和 onmessage

為了實現不同域之間的通信,需要在操作系統的 hosts 文件添加兩個域名,進行模擬。

清單 3. hosts 文件中添加兩個不同的域名
 127.0.0.1 	 parent.com 
 127.0.0.1 	 child.com

在父網頁中通過 iframe 嵌入子頁面,並在 JavaScript 代碼中調用 postMessage 方法發送數據到子窗口。

清單 4. 父頁面中嵌入子頁面,調用 postMessage 方法發送數據
<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 向子窗口發送數據
         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>

 

在子窗口中監聽 onmessage 事件,並用 JavaScript 實現顯示父窗口發送過來的數據。

清單 5. 子窗口中監聽 onmessage 事件,顯示父窗口發送來的數據
 <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 參數中有 data 屬性,就是父窗口發送過來的數據
     window.addEventListener("message", function( event ) { 
         // 把父窗口發送過來的數據顯示在子窗口中
       document.getElementById("content").innerHTML+=event.data+"<br/>"; 
     }, false ); 

 </script> 
 </head> 
 <body> 
     Web page from http://child.com:8080 
     <div id="content"></div> 
 </body> 
 </html>

 

圖 2. 父窗口嵌入子窗口

父窗口嵌入子窗口

圖 3. 父窗口發送數據到子窗口

父窗口發送數據到子窗口

WebSockets

WebSockets 簡介

在 Web 應用中,HTTP 協議決定了客戶端和服務端連接是短連接,即客戶端 Request,服務端 Response,連接斷開。要想實現客戶端和服務端實時通信,只能通過客戶端輪詢來實現。服務端推送數據也並不是字面上意思上的直接推,其實還是客戶端自己取。WebSockets 是 HTML5 規范新引入的功能,用於解決瀏覽器與后台服務器雙向通訊的問題,使用 WebSockets 技術,后台可以隨時向前端推送消息,以保證前后台狀態統一。

在 WebSockets 中使用 send 和 onmessage

由於文本主要介紹 postMessage(send) 和 onmessage 客戶端 API 的應用,而 WebSockets 涉及到服務器端代碼的實現,所以本文將選取最簡單的服務器端框架來編寫服務器代碼。WebSockets 服務器端有 jetty 提供的基於 Java 的實現,有 WebSocket-Node 基於 node.js 的實現,在 .Net 4.5 中也直接提供了 WebSockets 的支持。本文將使用 WebSocket-Node 提供的示例代碼,稍作修改作為 WebSockets 的服務器端。關於 node.js 的介紹以及使用請參考 node.js 官方網站 node.js,關於 WebSocket-Node 的使用請參考 WebSocket-Node

首先,需要在客戶端通過 JavaScript 代碼 new 一個 WebSocket 實例出來,參數是實現 WebSocket 服務器端 URL 地址。然后在這個實例上監聽 onmessage 事件接收服務器端發送過來的數據。當然,客戶端也可以調用 send 方法,發送數據到服務器端。

清單 6. 創建 WebSocket 對象,並監聽 onmessage 事件
 connect : function() { 
    var location ="ws://localhost:8000/"; 
     // 創建 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 方法中,接收數據,並顯示在頁面上

清單 7. _onmessage 方法
 _onmessage : function(event) { 
     //event 參數中有 data 屬性,就是服務器發送過來的數據
     if (event.data) { 
         var messageBox = document.getElementById('messageBox'); 
         var spanText = document.createElement('span'); 
         spanText.className ='text'; 
         // 把服務器發送過來的數據顯示在窗口中
         spanText.innerHTML = event.data; 
         var lineBreak = document.createElement('br'); 
         messageBox.appendChild(spanText); 
         messageBox.appendChild(lineBreak); 
         messageBox.scrollTop = messageBox.scrollHeight 
                 - messageBox.clientHeight; 
     } 
 },

 

在 _onopen 方法中,調用 _send 方法發送一條消息到服務器端,告之連接已經建立。在 _onclose 方法中,把 WebSocket 的實例設置成 null,釋放資源。

清單 8. _onopen,_onclose 以及 send 方法
_onopen : function() { 
     server._send("Client:Open WebSockets,"+new Date()); 
 }, 
 //message 參數就是客戶端向服務器端發送的數據
 _send : function(message) { 
     if (this._ws) 
         this._ws.send(message); 
 }, 
 // 此方法提供外部代碼調用
 send : function(text) { 
     if (text !=null&& text.length >0) 
         server._send(text); 
 }, 
 
 _onclose : function(m) { 
     this._ws =null; 
 }

 

把這些方法封裝在一個 server 對象中,方便提供外部調用。用戶只需要先調用 server 的 connect 方法建立連接,然后調用 send 方法發送數據。

清單 9. 封裝客戶端實現
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 示例即可。這里只展示關鍵代碼部分,其它代碼請參見 WebSocket-Node 示例。

清單 10. WebSockets 服務器端簡單實現
 // 監聽客戶端的連接請求
 wsServer.on('connect', function(connection) { 
    function sendCallback(err) { 
        if (err) console.error("send() error: " + err); 
    } 
     // 監聽客戶端發送數據的請求
    connection.on('message', function(message) { 
        if (message.type === 'utf8') {// 區別客戶端發過來的數據是文本還是二進制類型
            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) { 
    }); 
 });

 

圖 4. 點擊 Connect 按鈕

點擊 Connect 按鈕

圖 5. 輸入內容,單擊 Send Message 按鈕

輸入內容,單擊 Send Message 按鈕

Server-Sent Events

Server-Sent Events 簡介

HTML5 Server-Sent 事件模型允許您從服務器 push 實時數據到瀏覽器。本文我們將介紹利用 Eventsource 對象處理與頁面間的接收和發送數據。在客戶端,我們使用 HTML5+JavaScript,服務端使用 Java。在現存的 Ajax 模式中,web 頁面會持續不斷地請求服務器傳輸新數據,由客戶端負責請求數據。而在服務端發送模式下,無需在客戶端代碼中執行連續的數據請求,而是由服務端 push 推送更新。一旦您在頁面中初始化了 Server-Sent 事件,服務端腳本將持續地發送更新。客戶端 JavaScript 代碼一旦接收到更新就將新的數據寫入頁面中展示出來。

在 Server-Sent Events 中使用 onmessage

Server-Sent Events 和 WebSockets 有相同之處,WebSockets 實現了服務器端以及客戶端的雙向通信功能,而 Server-Sent Events 則僅是指服務器端到客戶端的單向通信,而且 Server-Sent Events 同樣需要服務器端的實現,本文將使用基於 Java 的 Servlet 技術實現服務器端。關於服務器端向客戶端寫數據的格式,可以參考 W3C 關於 Server-Sent Events 的規范文檔 Server-Sent Events。由於是服務器端到客戶端的單向通信,所以在 Server-Sent Events 中沒有 postMessage 方法。

首先,在客戶端通過 JavaScript 代碼 new 一個 EventSource 實例出來,參數是實現 EventSource 服務器端 URL 地址。然后在這個實例上監聽 onmessage 事件接收服務器端發送過來的數據。

清單 11. 創建 EventSource 對象,並監聽 onmessage 事件
 if (!!window.EventSource) { 
 // 創建 EventSource 實例,傳入 server 地址
      var source = new EventSource('/TestHTML5/ServerSentEvent'); 
     } else { 
      console.log("Your browser doesn't support server-sent event"); 
 } 
 // 監聽 message 事件,等待接收服務器端發送過來的數據
 source.addEventListener('message', function(event) { 
         //event 參數中有 data 屬性,就是服務器發送過來的數據
      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 語言實現的 Servlet doGet 方法中使用 response 對象向客戶端寫數據

清單 10. 服務器端簡單實現
 // 這里必須設置 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); 
 // 向客戶端寫兩行數據
 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();

 

圖 6. Server-Sent Events 運行結果

Server-Sent Events 運行結果

為什么比cookie好

1. 從容量上講WebStorage一般瀏覽器提供5M的存儲空間,用來存儲視頻、圖片神馬的不夠,但對於絕大部分操作足矣

2.安全性上WebStorage並不作為HTTP header發送的瀏覽器,所以相對安全

3.從流量上講,因為WebStorage不傳送到服務器,所以不必要的流量可以節省,這樣對於高頻次訪問或者針對手機移動設備的網頁還是很不錯的。

這並不意味着WebStorage可以取代cookie,而是有了WebStorage后cookie能只做它應該做的事情了——作為客戶端與服務器交互的通道,保持客戶端狀態。所以僅僅作為本地存儲解決方案WebStorage是優於cookie的。

注意點

1.瀏覽器兼容性,這個幾乎是所有HTML5新特性中最容易實施的了,因為IE8+的瀏覽器都支持,在IE7、IE6中可以使用IE User Data實現。

2011052411384081

2. 由於localStorage和sessionStorage都是對象,所以我餓每年也可以通過”.key”或”[key]”的方式獲取、修改鍵值對,但不推薦這么做

localStorage.userName='Frank';
console.log(localStorage['userName']);

3.雖然localStorage存儲在本地,但不同的瀏覽器存儲存儲數據是獨立的,所以在Chrome上存儲的localStorage在FireFox上是獲取不到的。

4. localStorage和sessionStorage只能存儲字符串類型,對於復雜的對象可以使用ECMAScript提供的JSON對象的stringify和parse來處理,低版本IE可以使用json2.js

5.除了控制台,Chrome還為本地存儲提供了非常直觀的顯示方式,調試的時候很方便

image

結束語

本文詳細介紹了 postMessage(send)和 onmessage API 在客戶端的應用情況,可以看到在不同的場景中這兩個方法的應用模式都是類似的。postMessage 的作用就是傳遞數據,而 onmessage 的作用就是接收數據。掌握此組 API 對以后開發 HTML 5 應用程序將會有所幫助。本文 Web Workers,Cross-document messaging,WebSockets 的代碼在 Firefox 14 下通過測試,Server-Sent Events 的代碼在 Chrome 16 下通過測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM