pbootcms网站模板|日韩1区2区|织梦模板||网站源码|日韩1区2区|jquery建站特效-html5模板网

HTML5移動(dòng)應(yīng)用開發(fā)第4章:Web Workers來加速您的移動(dòng)

在本文中,您將使用最新的 Web 技術(shù)開發(fā) Web 應(yīng)用程序。這里的 大部分代碼只是 HTML、JavaScript 和 CSS — 所有 Web 開發(fā)人員的核心技術(shù)。所需的最重要的工具是用于進(jìn)行測(cè)試的瀏覽器。本文大部分代碼將在最新桌面瀏覽器上運(yùn)行,但也有一些例外,我們將在文章中進(jìn)行說明。當(dāng)然,您也必須在移動(dòng)瀏覽器上測(cè)試,為此,您需要最新的 iPhone 和 Android SDKs。本文將使用 iPhone SDK 3.1.3 和 Android SDK 2.1。本文的樣例還將使用一個(gè)代理服務(wù)器來從瀏覽器訪問遠(yuǎn)程服務(wù)。這個(gè)代理服務(wù)器是一個(gè)簡(jiǎn)單的 Java™ servlet,但也可以使用以 PHP、Ruby 以及其他語(yǔ)言編寫的代理輕松替換。

移動(dòng)設(shè)備上的多線程 JavaScript

對(duì)于大多數(shù)開發(fā)人員來說,多線程或并發(fā)編程并不新鮮。但是,JavaScript 并不是一種支持并發(fā)編程的語(yǔ)言。JavaScript 的創(chuàng)建者認(rèn)為,對(duì)于 JavaScript 這樣旨在 Web 頁(yè)面上執(zhí)行簡(jiǎn)單任務(wù)的語(yǔ)言來說,并發(fā)編程容易出現(xiàn)問題,而且沒有必要。然而,由于 Web 頁(yè)面已經(jīng)發(fā)展成為 Web 應(yīng)用程序,使用 JavaScript 完成的任務(wù)的復(fù)雜程度已經(jīng)大大增加,向 JavaScript 提出了與其他語(yǔ)言同等的要求。與此同時(shí),使用其他支持并發(fā)編程的語(yǔ)言工作的開發(fā)人員經(jīng)常面臨伴隨線程和 mutexes 這樣的并發(fā)原語(yǔ)而來的超高復(fù)雜性的困擾。實(shí)際上,最近像 Scala、Clojure 和 F# 這樣的幾種新語(yǔ)言已經(jīng)發(fā)展,它們都有可能簡(jiǎn)化并發(fā)性。

常用縮略詞
  • Ajax:異步 JavaScript + XML
  • API:應(yīng)用程序編程接口
  • CSS:層疊樣式表
  • DOM:文檔對(duì)象模型
  • HTML:超文本標(biāo)記語(yǔ)言
  • REST:具象狀態(tài)傳輸
  • SDK:軟件開發(fā)工具包
  • UI:用戶界面
  • URL:統(tǒng)一資源定位符
  • W3C:萬(wàn)維網(wǎng)聯(lián)盟
  • XML:可擴(kuò)展標(biāo)記語(yǔ)言

Web Worker 規(guī)范不只是向 JavaScript 和 Web 瀏覽器添加并發(fā)性,而且是以一種智慧的方式添加,這種方式將增加開發(fā)人員的能力,但不會(huì)向他們提供一種會(huì)導(dǎo)致問題的工具。 例如,多年來,桌面應(yīng)用程序開發(fā)人員一直在使用多線程來支持他們的應(yīng)用程序訪問多個(gè) I/O 資源,以避免在等待這些資源時(shí)凍結(jié) UI。然而,當(dāng)這些多線程更改共享的資源(包括 UI)時(shí),這樣的應(yīng)用程序通常會(huì)出現(xiàn)問題,因?yàn)檫@種行為可能會(huì)導(dǎo)致應(yīng)用程序凍結(jié)或崩潰。有了 Web Workers,這種情況就不會(huì)發(fā)生。衍生線程不能訪問主 UI 線程訪問的資源。事實(shí)上,衍生線程中的代碼甚至不能與主 UI 線程執(zhí)行的代碼位于同一個(gè)文件中。

您甚至必須提供相應(yīng)的外部文件作為構(gòu)造函數(shù)的一部分,如 清單 1 所示。

這個(gè)進(jìn)程使用三個(gè)資源:

  1. 在主線程上執(zhí)行的 Web 頁(yè)面 JavaScript(我稱其為頁(yè)面腳本)。
  2. Worker 對(duì)象,這是用于執(zhí)行 Web Worker 函數(shù)的 JavaScript 對(duì)象。
  3. 將在新衍生的線程上執(zhí)行的腳本。我稱其為 Worker 腳本。

讓我們首先看看 清單 1 中的頁(yè)面腳本。


清單 1.在頁(yè)面腳本中使用一個(gè) Web Worker

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. var worker = new Worker("worker.js");  
  2. worker.onmessage = function(message){  
  3.     // do stuff  
  4. };  
  5. worker.postMessage(someDataToDoStuffWith);  

 

在 清單 1 中,您可以看到使用 Web Workers 的三個(gè)基本步驟。首先,您創(chuàng)建一個(gè) Worker 對(duì)象并向它傳遞將在新線程中執(zhí)行的腳本的 URL。Worker 將執(zhí)行的所有代碼都必須包含在一個(gè) Worker 腳本中,該腳本的 URL 將被傳遞到這個(gè) Worker 的構(gòu)造函數(shù)中。這個(gè) Worker 腳本的 URL 受到瀏覽器的同源策略的限制 — 它必須來自加載這個(gè)頁(yè)面的同一個(gè)域,該頁(yè)面已加載正在創(chuàng)建這個(gè) Web Worker 的頁(yè)面腳本。

下一步是使用 onmessage 函數(shù)指定一個(gè)回調(diào)處理器函數(shù)。這個(gè)回調(diào)函數(shù)將在該 Worker 腳本執(zhí)行后調(diào)用。message 是從該 Worker 腳本返回的數(shù)據(jù),您可以隨意處理該消息。回調(diào)函數(shù)在主線程上執(zhí)行,因此它能訪問 DOM。Worker 腳本在一個(gè)不同的線程內(nèi)運(yùn)行且不能訪問 DOM,因此,您需要將來自這個(gè) Worker 腳本的數(shù)據(jù)返回主線程,在那里,您可以安全地修改 DOM 來更新您的應(yīng)用程序的 UI。這是 Web Workers 的無共享設(shè)計(jì)的關(guān)鍵特性。

清單 1 中的最后一行展示如何通過調(diào)用 Worker 的 postMessage 函數(shù)來啟動(dòng)它。這里,您傳遞一條消息(重申一下,它只是數(shù)據(jù))給 Worker。當(dāng)然,postMessage 是一個(gè)異步函數(shù);您調(diào)用它,它就立即返回。

現(xiàn)在,檢查這個(gè) Worker 腳本。清單 2 中的代碼是來自 清單 1 的 worker.js 文件的內(nèi)容。


清單 2. 一個(gè) Worker 腳本

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. importScripts("utils.js");  
  2. var workerState = {};  
  3. onmessage = function(message){  
  4.      workerState = message.data;  
  5.       // do stuff with the message  
  6.     postMessage({responseXml: this.responseText});  
  7. }  

 

可以看到,這個(gè) Worker 腳本擁有自己的 onmessage 函數(shù)。該函數(shù)在您從主線程調(diào)用 postMessage 時(shí)調(diào)用。從頁(yè)面腳本傳來的數(shù)據(jù)被傳遞到 message 對(duì)象中的 postMessage 函數(shù)。您通過檢索 message 對(duì)象的 data 屬性來訪問該數(shù)據(jù)。當(dāng)您處理完 Worker 腳本中的數(shù)據(jù)時(shí),調(diào)用 postMessage 函數(shù)將數(shù)據(jù)返回主線程。主線程也可以通過訪問它接收到的消息的 data 屬性來訪問該數(shù)據(jù)。

至此,您已經(jīng)見識(shí)了 Web Workers 的這個(gè)簡(jiǎn)單、但強(qiáng)大的語(yǔ)義。接下來,您將了解如何應(yīng)用這個(gè)語(yǔ)義來加速移動(dòng) Web 應(yīng)用程序。在此之前,有必要先討論一下設(shè)備支持。畢竟,這些是移動(dòng) Web 應(yīng)用程序,且處理不同瀏覽器之間的功能的區(qū)別對(duì)于移動(dòng) Web 應(yīng)用程序開發(fā)很重要。

設(shè)備支持

從 Android 2.0 開始,Android 瀏覽器就擁有了對(duì) HTML 5 Web Worker 規(guī)范的全面支持。在撰寫本文之時(shí),最新的 Android 設(shè)備(包括非常流行的 Motorola Droid)已配置了 Android 2.1。另外,此特性在運(yùn)行 Maemo 操作系統(tǒng)的 Nokia 設(shè)備上的 Mozilla Fennec 瀏覽器以及 Windows Mobile 設(shè)備上受到完全支持。這里需要引起注意的遺漏是 iPhone。iPhone OS 3.1.3 和 3.2 版(在 iPad 上運(yùn)行的 OS 的版本)并不支持 Web Workers。但是,此特性已在 Safari 上受到支持,因此,此特性在運(yùn)行在 iPhone 上的 Mobile Safari 瀏覽器上出現(xiàn)應(yīng)該只是一個(gè)時(shí)間問題。鑒于 iPhone 的主導(dǎo)地位(尤其是在美國(guó)),最好不要依賴 Web Workers 的存在,且不要只在您檢測(cè)到它們的存在時(shí)才使用它們來增強(qiáng)您的移動(dòng) Web 應(yīng)用程序。意識(shí)到這一點(diǎn)后,我們來看看如何使用 Web Workers 來加速您的移動(dòng) Web 應(yīng)用程序。

使用 Workers 改善性能

智能手機(jī)瀏覽器上的 Web Worker 支持很不錯(cuò),而且一直在不斷改進(jìn)。這就提出了一個(gè)問題:什么時(shí)候需要在移動(dòng) Web 應(yīng)用程序中使用 Workers?答案很簡(jiǎn)單:需要完成耗時(shí)的任務(wù)的任何時(shí)候。有些示例展示了如何將 Workers 用于執(zhí)行密集的數(shù)學(xué)計(jì)算,比如計(jì)算 1 萬(wàn)位數(shù)的圓周率。很可能您永遠(yuǎn)也不需要在 Web 應(yīng)用程序上執(zhí)行這樣一個(gè)計(jì)算,在移動(dòng) Web 應(yīng)用程序上執(zhí)行這種計(jì)算的幾率則更小。但是,從遠(yuǎn)程資源檢索數(shù)據(jù)則相當(dāng)常見,這也是本文示例的關(guān)注點(diǎn)。

在這個(gè)示例中,您將從 eBay 檢索一個(gè) Daily Deals(每天都在變化的交易)列表。這個(gè)交易列表包含關(guān)于每筆交易的簡(jiǎn)短信息。更詳細(xì)的信息可以通過使用 eBay 的 Shopping API 獲取。當(dāng)用戶瀏覽這個(gè)交易列表選擇感興趣的商品時(shí),您將使用 Web Workers 來預(yù)取這個(gè)附加信息。要從您的 Web 應(yīng)用程序訪問所有這些 eBay 數(shù)據(jù),您需要通過使用一個(gè)泛型代理(generic proxy)來處理瀏覽器的同源策略。一個(gè)簡(jiǎn)單的 Java servlet 將用于這個(gè)代理,它包含在本文的代碼中,但不在這里單獨(dú)展示。相反,我們將把注意力集中在處理 Web Workers 的代碼上。清單 3 展示了這個(gè)交易應(yīng)用程序的基本 HTML 頁(yè)面。

清單 3. 交易應(yīng)用程序 HTML

XML/HTML Code復(fù)制內(nèi)容到剪貼板
  1. <!DOCTYPE HTML>  
  2. <html>  
  3.   <head>  
  4.     <meta http-equiv="content-type" content="text/html; charset=UTF-8">  
  5.     <meta name = "viewport" content = "width = device-width">  
  6.     <title>Worker Deals</title>  
  7.     <script type="text/javascript" src="common.js"></script>  
  8.   </head>  
  9.   <body onload="loadDeals()">  
  10.     <h1>Deals</h1>  
  11.     <ol id="deals">  
  12.     </ol>  
  13.     <h2>More Deals</h2>  
  14.     <ul id="moreDeals">  
  15.     </ul>  
  16.   </body>  
  17. </html>  

 

可以看出,這是一段非常簡(jiǎn)單的 HTML;它只是一個(gè) shell。您使用 JavaScript 檢索數(shù)據(jù)并生成 UI。這是移動(dòng) Web 應(yīng)用程序的優(yōu)化設(shè)計(jì),因?yàn)樗试S將所有代碼和靜態(tài)標(biāo)記緩存到設(shè)備上,用戶只需等待來自服務(wù)器的數(shù)據(jù)。注意,在 清單 3 中,一旦那個(gè) body 加載,您就調(diào)用 loadDeals 函數(shù),在那里,您將加載 清單 4 中的應(yīng)用程序的初始數(shù)據(jù)。


清單 4. loadDeals 函數(shù)

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. var deals = [];  
  2. var sections = [];  
  3. var dealDetails = {};  
  4. var dealsUrl = "http://deals.ebay.com/feeds/xml";  
  5. function loadDeals(){  
  6.     var xhr = new XMLHttpRequest();  
  7.     xhr.onreadystatechange = function(){  
  8.         if (this.readyState == 4 && this.status == 200){  
  9.                var i = 0;  
  10.                var j = 0;  
  11.                var dealsXml = this.responseXML.firstChild;  
  12.                var childNode = {};  
  13.                for (i=0; i< dealsXml.childNodes.length;i++){  
  14.                    childNode = dealsXml.childNodes.item(i);  
  15.                    switch(childNode.localName){  
  16.                    case 'Item':   
  17.                        deals.push(parseDeal(childNode));  
  18.                        break;  
  19.                    case "MoreDeals":  
  20.                        for (j=0;j<childNode.childNodes.length;j++){  
  21.                            var sectionXml= childNode.childNodes.item(j);  
  22.                            if (sectionXml && sectionXml.hasChildNodes()){  
  23.                                sections.push(parseSection(sectionXml));  
  24.                            }  
  25.                        }  
  26.                        break;      
  27.                    default:  
  28.                        break;  
  29.                    }  
  30.                }  
  31.                deals.forEach(function(deal){  
  32.                    var entry = createDealUi(deal);  
  33.                    $("deals").appendChild(entry);  
  34.                });  
  35.                loadDetails(deals);  
  36.                sections.forEach(function(section){  
  37.                    var ui = createSectionUi(section);  
  38.                    $("moreDeals").appendChild(ui);  
  39.                    loadDetails(section.deals);  
  40.                });  
  41.         }  
  42.     };  
  43.     xhr.open("GET""proxy?url=" + escape(dealsUrl));  
  44.     xhr.send(null);  
  45. }  

 

清單 4 展示了 loadDeals 函數(shù),以及應(yīng)用程序中使用的全局變量。您使用了一個(gè) deals 數(shù)組和一個(gè) sections 數(shù)組。它們是相關(guān)交易的附加組(例如,Deals under $10)。還有一個(gè)名為 dealDetails 的映射,其鍵是 Item IDs(來自于交易數(shù)據(jù)),其值是從 eBay Shopping API 獲取的詳細(xì)信息。

您首先調(diào)用一個(gè)代理,該代理又將調(diào)用 eBay Daily Deals REST API。這將把交易列表作為一個(gè) XML 文檔提供給您。您解析用于進(jìn)行 Ajax 調(diào)用的 XMLHttpRequest 對(duì)象的 onreadystatechange 函數(shù)中的文檔。您還使用其他兩個(gè)函數(shù),parseDeal 和 parseSection,來將 XML 節(jié)點(diǎn)解析為更易于使用的 JavaScript 對(duì)象。這些函數(shù)可以在可下載的代碼樣例(參見 下載 部分)中找到,但由于它們只是令人厭煩的 XML 解析函數(shù),因此我在這里沒有包括它們。最后,在解析了 XML 后,您還使用了另外兩個(gè)函數(shù),createDealUi 和createSectionUi,來修改 DOM。此時(shí),這個(gè) UI 如 圖 1 所示。


圖 1. Mobile Deals UI
帶有樣例交易的 Mobile Deals UI 的屏幕截圖,每個(gè)交易都有一個(gè) Show Details 按鈕 

如果您返回 清單 4,就會(huì)注意到在加載主交易之后,您對(duì)這些交易的每個(gè)部分都調(diào)用了 loadDetails 函數(shù)。在這個(gè)函數(shù)中,您通過使用 eBay Shopping API 加載每個(gè)交易的附加細(xì)節(jié) — 但前提是瀏覽器支持 Web Workers。清單 5 展示了 loadDetails 函數(shù)。


清單 5. 預(yù)取交易細(xì)節(jié)

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. function loadDetails(items){  
  2.     if (!!window.Worker){  
  3.         items.forEach(function(item){  
  4.             var xmlStr = null;  
  5.             if (window.localStorage){  
  6.                 xmlStr = localStorage.getItem(item.itemId);  
  7.             }  
  8.             if (xmlStr){  
  9.                 var itemDetails = parseFromXml(xmlStr);  
  10.                 dealDetails[itemDetails.id] = itemDetails;  
  11.             } else {  
  12.                 var worker = new Worker("details.js");  
  13.                 worker.onmessage = function(message){  
  14.                     var responseXmlStr =message.data.responseXml;  
  15.                     var itemDetails=parseFromXml(responseXmlStr);  
  16.                     if (window.localStorage){  
  17.                         localStorage.setItem(  
  18.                                         itemDetails.id, responseXmlStr);  
  19.                     }  
  20.                     dealDetails[itemDetails.id] = itemDetails;  
  21.                 };  
  22.                     worker.postMessage(item.itemId);  
  23.             }  
  24.         });  
  25.     }  
  26. }  

 

在 loadDetails 中,您首先檢查全局作用域(window 對(duì)象)中的 Worker 函數(shù)。如果該函數(shù)不在那里,那么無需做任何事。反之,您首先檢查 XML 的 localStorage 以獲取這個(gè)交易的細(xì)節(jié)。這是移動(dòng) Web 應(yīng)用程序常用的本地緩存策略,本系列第 2 部分(參見 參考資料 部分的鏈接)詳細(xì)介紹過這種策略。

如果 XML 位于本地,那么您在 parseFromXml 函數(shù)中解析它并將交易細(xì)節(jié)添加到 dealDetails 對(duì)象。反之,則衍生一個(gè) Web Worker 并使用 postMessage 向其發(fā)送 Item ID。當(dāng)這個(gè) Worker 檢索到數(shù)據(jù)并將數(shù)據(jù)發(fā)布回主線程后,您解析 XML,將結(jié)果添加到dealDetails,然后將 XML 存儲(chǔ)到 localStorage 中。清單 6 展示了這個(gè) Worker 腳本:details.js。


清單 6. 交易細(xì)節(jié) Worker 腳本

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. importScripts("common.js");  
  2. onmessage = function(message){  
  3.     var itemId = message.data;  
  4.     var xhr = new XMLHttpRequest();  
  5.     xhr.onreadystatechange = function(){  
  6.         if (this.readyState == 4 && this.status == 200){  
  7.             postMessage({responseXml: this.responseText});  
  8.         }  
  9.     };  
  10.     var urlStr = generateUrl(itemId);  
  11.     xhr.open("GET""proxy?url=" + escape(urlStr));  
  12.     xhr.send(null);  
  13. }  

這個(gè) Worker 腳本非常簡(jiǎn)單。您使用 Ajax 調(diào)用代理,該代理又調(diào)用 eBay Shopping API。當(dāng)您收到來自代理的 XML 后,使用一個(gè) JavaScript 對(duì)象文字(object literal)將其發(fā)送回主線程。注意,即使您能夠使用來自一個(gè) Worker 的 XMLHttpRequest,但所有信息都將返回它的 responseText 屬性,而不是它的 responseXml 屬性。這是因?yàn)檫@個(gè) Worker 腳本范圍內(nèi)沒有 JavaScript DOM 解析器。注意,generateUrl 函數(shù)來自 common.js 文件(見 清單 7)。您使用 importScripts 函數(shù)導(dǎo)入 common.js 文件。


清單 7. Worker 導(dǎo)入的腳本

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. function generateUrl(itemId){  
  2.     var appId = "YOUR APP ID GOES HERE";  
  3.     return "http://open.api.ebay.com/shopping?callname=GetSingleItem&"+  
  4.         "responseencoding=XML&appid=" + appId + "&siteid=0&version=665"  
  5.             +"&ItemID=" + itemId;  
  6. }  

 

現(xiàn)在,您已經(jīng)知道如何(為支持 Web Workers 的瀏覽器)填充交易細(xì)節(jié),我們返回 圖 1 研究一下如何在應(yīng)用程序中使用這種方法。注意,每筆交易旁邊都有一個(gè) Show Details 按鈕,單擊該按鈕修改這個(gè) UI,如 圖 2 所示。


圖 2. 顯示的交易細(xì)節(jié)
顯示交易細(xì)節(jié)的屏幕截圖,包含兩個(gè) Golla 小包(MP3、移動(dòng)電話和相機(jī))的產(chǎn)品說明、圖片和價(jià)格 

這個(gè) UI 將在您調(diào)用 showDetails 函數(shù)時(shí)顯示。清單 8 展示了這個(gè)函數(shù)。


清單 8. showDetails 函數(shù)

JavaScript Code復(fù)制內(nèi)容到剪貼板
  1. function showDetails(id){  
  2.     var el = $(id);  
  3.     if (el.style.display == "block"){  
  4.         el.style.display = "none";  
  5.     } else {  
  6.         el.style.display = "block";  
  7.         if (!el.innerHTML){  
  8.             var details = dealDetails[id];  
  9.             if (details){  
  10.                 var ui = createDetailUi(details);  
  11.                 el.appendChild(ui);  
  12.             } else {  
  13.                 var itemId = id;  
  14.                 var xhr = new XMLHttpRequest();  
  15.                 xhr.onreadystatechange = function(){  
  16.                     if (this.readyState == 4 &&   
  17.                                       this.status == 200){  
  18.                         var itemDetails =   
  19.                                         parseFromXml(this.responseText);  
  20.                         if (window.localStorage){  
  21.                             localStorage.setItem(  
  22.                                               itemDetails.id,   
  23.                                               this.responseText);  
  24.                         }  
  25.                         dealDetails[id] = itemDetails;  
  26.                         var ui = createDetailUi(itemDetails);  
  27.                         el.appendChild(ui);  
  28.                     }  
  29.                 };  
  30.                 var urlStr = generateUrl(id);  
  31.                 xhr.open("GET""proxy?url=" + escape(urlStr));  
  32.                 xhr.send(null);                          
  33.             }  
  34.         }  
  35.     }  
  36. }  

 

您收到了即將顯示的交易的 ID 并切換是否顯示它。當(dāng)該函數(shù)第一次調(diào)用時(shí),它將檢查細(xì)節(jié)是否已經(jīng)存儲(chǔ)到 dealDetails 映射中。如果瀏覽器支持 Web Workers,那么這些細(xì)節(jié)已經(jīng)存儲(chǔ)且它的 UI 已經(jīng)創(chuàng)建并添加到 DOM 中。如果這些細(xì)節(jié)還沒有加載,或者,如果瀏覽器不支持 Workers,那么您需要執(zhí)行一個(gè) Ajax 調(diào)用來加載此數(shù)據(jù)。這就是這個(gè)應(yīng)用程序無論在有無 Workers 時(shí)都同樣能正常工作的原因。這意味著,如果 Workers 受到支持,那么數(shù)據(jù)就已被加載且 UI 將立即響應(yīng)。如果沒有 Workers,UI 仍將加載,只是需要花費(fèi)幾秒鐘時(shí)間。

結(jié)束語(yǔ)

對(duì)于 Web 開發(fā)人員來說,Web Workers 聽起來就像一種外來的新技術(shù)。但是,如本文所述,它們是非常實(shí)用的應(yīng)用程序。這對(duì)于移動(dòng) Web 應(yīng)用程序來說尤其正確。這些 Workers 可用于預(yù)取數(shù)據(jù)或執(zhí)行其他預(yù)先操作,從而提供一個(gè)更加實(shí)時(shí)的 UI。這對(duì)于需要通過網(wǎng)速可能較慢的網(wǎng)絡(luò)加載數(shù)據(jù)的移動(dòng) Web 應(yīng)用程序來說尤其正確。結(jié)合使用這種技術(shù)和緩存策略,您的應(yīng)用程序的快捷反應(yīng)將使您的用戶感到驚喜!

【網(wǎng)站聲明】本站除付費(fèi)源碼經(jīng)過測(cè)試外,其他素材未做測(cè)試,不保證完整性,網(wǎng)站上部分源碼僅限學(xué)習(xí)交流,請(qǐng)勿用于商業(yè)用途。如損害你的權(quán)益請(qǐng)聯(lián)系客服QQ:2655101040 給予處理,謝謝支持。

相關(guān)文檔推薦

這篇文章主要介紹了有關(guān)HTML5頁(yè)面在iPhoneX適配問題,需要的朋友可以參考下
本篇文章主要介紹了html5中canvas圖表實(shí)現(xiàn)柱狀圖的示例,本文使用canvas來實(shí)現(xiàn)一個(gè)圖表,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
Adobe公司出品的多媒體處理軟件產(chǎn)品線較多,涵蓋了音視頻編輯、圖像處理、平面設(shè)計(jì)、影視后期等領(lǐng)域。這篇文章主要介紹了Adobe Html5 Extension開發(fā)初體驗(yàn)圖文教程,非常不錯(cuò),需要的朋
這篇文章主要介紹了基于HTML5的WebGL經(jīng)典3D虛擬機(jī)房漫游動(dòng)畫,需要的朋友可以參考下
這篇文章主要介紹了手機(jī)端用rem+scss做適配的詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
本篇文章主要介紹了canvas 實(shí)現(xiàn) github404動(dòng)態(tài)效果的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
主站蜘蛛池模板: 紧急切断阀_气动切断阀_不锈钢阀门_截止阀_球阀_蝶阀_闸阀-上海上兆阀门制造有限公司 | 精密机械零件加工_CNC加工_精密加工_数控车床加工_精密机械加工_机械零部件加工厂 | 东莞动力锂电池保护板_BMS智能软件保护板_锂电池主动均衡保护板-东莞市倡芯电子科技有限公司 | 海鲜池-专注海鲜鱼缸、移动海鲜缸、饭店鱼缸设计定做-日晟水族厂家 | 技德应用| 橡胶粉碎机_橡胶磨粉机_轮胎粉碎机_轮胎磨粉机-河南鼎聚重工机械制造有限公司 | 巨野电机维修-水泵维修-巨野县飞宇机电维修有限公司 | 多米诺-多米诺世界纪录团队-多米诺世界-多米诺团队培训-多米诺公关活动-多米诺创意广告-多米诺大型表演-多米诺专业赛事 | 河南空气能热水器-洛阳空气能采暖-洛阳太阳能热水工程-洛阳润达高科空气能商行 | DWS物流设备_扫码称重量方一体机_快递包裹分拣机_广东高臻智能装备有限公司 | 塑料薄膜_PP薄膜_聚乙烯薄膜-常州市鑫美新材料包装厂 | Duoguan 夺冠集团 | 冰晶石|碱性嫩黄闪蒸干燥机-有机垃圾烘干设备-草酸钙盘式干燥机-常州市宝康干燥 | Maneurop/美优乐压缩机,活塞压缩机,型号规格,技术参数,尺寸图片,价格经销商 | 学生作文网_中小学生作文大全与写作指导 | 探伤仪,漆膜厚度测试仪,轮胎花纹深度尺厂家-淄博创宇电子 | 面粉仓_储酒罐_不锈钢储酒罐厂家-泰安鑫佳机械制造有限公司 | 矿用履带式平板车|探水钻机|气动架柱式钻机|架柱式液压回转钻机|履带式钻机-启睿探水钻机厂家 | 江苏密集柜_电动_手动_移动_盛隆柜业江苏档案密集柜厂家 | 法钢特种钢材(上海)有限公司 - 耐磨钢板、高强度钢板销售加工 阀门智能定位器_电液动执行器_气动执行机构-赫尔法流体技术(北京)有限公司 | 废气处理_废气处理设备_工业废气处理_江苏龙泰环保设备制造有限公司 | 新疆系统集成_新疆系统集成公司_系统集成项目-新疆利成科技 | 济宁工业提升门|济宁电动防火门|济宁快速堆积门-济宁市统一电动门有限公司 | wika威卡压力表-wika压力变送器-德国wika代理-威卡总代-北京博朗宁科技 | HDPE储罐_厂家-山东九州阿丽贝防腐设备 | 中天寰创-内蒙古钢结构厂家|门式刚架|钢结构桁架|钢结构框架|包头钢结构煤棚 | 谈股票-今日股票行情走势分析-牛股推荐排行榜 | 油漆辅料厂家_阴阳脚线_艺术漆厂家_内外墙涂料施工_乳胶漆专用防霉腻子粉_轻质粉刷石膏-魔法涂涂 | 权威废金属|废塑料|废纸|废铜|废钢价格|再生资源回收行情报价中心-中废网 | 电动不锈钢套筒阀-球面偏置气动钟阀-三通换向阀止回阀-永嘉鸿宇阀门有限公司 | MTK核心板|MTK开发板|MTK模块|4G核心板|4G模块|5G核心板|5G模块|安卓核心板|安卓模块|高通核心板-深圳市新移科技有限公司 | 高效复合碳源-多核碳源生产厂家-污水处理反硝化菌种一长隆科技库巴鲁 | 超声波焊接机_超音波熔接机_超声波塑焊机十大品牌_塑料超声波焊接设备厂家 | 全自动烧卖机厂家_饺子机_烧麦机价格_小笼汤包机_宁波江北阜欣食品机械有限公司 | 干粉砂浆设备_干混砂浆生产线_腻子粉加工设备_石膏抹灰砂浆生产成套设备厂家_干粉混合设备_砂子烘干机--郑州铭将机械设备有限公司 | 冷库安装厂家_杭州冷库_保鲜库建设-浙江克冷制冷设备有限公司 | 体感VRAR全息沉浸式3D投影多媒体展厅展会游戏互动-万展互动 | 蜘蛛车-高空作业平台-升降机-高空作业车租赁-臂式伸缩臂叉装车-登高车出租厂家 - 普雷斯特机械设备(北京)有限公司 | 深圳市超时尚职业培训学校,培训:月嫂,育婴,养老,家政;化妆,美容,美发,美甲. | YAGEO国巨电容|贴片电阻|电容价格|三星代理商-深圳市巨优电子有限公司 | 威廉希尔WilliamHill·足球(中国)体育官方网站 |