清单 4. loadDeals
函数
var deals = [];
var sections = [];
var dealDetails = {};
var dealsUrl = "http://deals.ebay.com/feeds/xml";
function loadDeals(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var i = 0;
var j = 0;
var dealsXml = this.responseXML.firstChild;
var childNode = {};
for (i=0; i< dealsXml.childNodes.length;i++){
childNode = dealsXml.childNodes.item(i);
switch(childNode.localName){
case 'Item':
deals.push(parseDeal(childNode));
break;
case "MoreDeals":
for (j=0;j var sectionXml= childNode.childNodes.item(j);if (sectionXml && sectionXml.hasChildNodes()){sections.push(parseSection(sectionXml));}}break;default:break;}}deals.forEach(function(deal){var entry = createDealUi(deal);$("deals").appendChild(entry);});loadDetails(deals);sections.forEach(function(section){var ui = createSectionUi(section);$("moreDeals").appendChild(ui);loadDetails(section.deals);});}};xhr.open("GET", "proxy?url=" + escape(dealsUrl));xhr.send(null);}清单 4 展示了
loadDeals
函数,以及应用程序中使用的全局变量。您使用了一个 deals 数组和一个 sections 数组。它们是相关交易的附加组(例如,Deals under $10
)。还有一个名为dealDetails
的映射,其键是 Item IDs(来自于交易数据),其值是从 eBay Shopping API 获取的详细信息。您首先调用一个代理,该代理又将调用 eBay Daily Deals REST API。这将把交易列表作为一个 XML 文档提供给您。您解析用于进行 Ajax 调用的 XMLHttpRequest 对象的
onreadystatechange
函数中的文档。您还使用其他两个函数,parseDeal
和parseSection
,来将 XML 节点解析为更易于使用的 JavaScript 对象,但由于它们只是令人厌烦的 XML 解析函数,因此我在这里没有包括它们。最后,在解析了 XML 后,您还使用了另外两个函数,createDealUi
和createSectionUi
,来修改 DOM。此时,这个 UI 如 图 1 所示。如果您返回 清单 4,就会注意到在加载主交易之后,您对这些交易的每个部分都调用了
loadDetails
函数。在这个函数中,您通过使用 eBay Shopping API 加载每个交易的附加细节 — 但前提是浏览器支持 Web Workers。清单 5 展示了loadDetails
函数。清单 5. 预取交易细节
function loadDetails(items){if (!!window.Worker){items.forEach(function(item){var xmlStr = null;if (window.localStorage){xmlStr = localStorage.getItem(item.itemId);}if (xmlStr){var itemDetails = parseFromXml(xmlStr);dealDetails[itemDetails.id] = itemDetails;} else {var worker = new Worker("details.js");worker.onmessage = function(message){var responseXmlStr =message.data.responseXml;var itemDetails=parseFromXml(responseXmlStr);if (window.localStorage){localStorage.setItem(itemDetails.id, responseXmlStr);}dealDetails[itemDetails.id] = itemDetails;};worker.postMessage(item.itemId);}});}}在
loadDetails
中,您首先检查全局作用域(window
对象)中的Worker
函数。如果该函数不在那里,那么无需做任何事。反之,您首先检查 XML 的localStorage
以获取这个交易的细节。这是移动 Web 应用程序常用的本地缓存策略,本系列第 2 部分.详细介绍过这种策略。如果 XML 位于本地,那么您在
parseFromXml
函数中解析它并将交易细节添加到dealDetails
对象。反之,则衍生一个 Web Worker 并使用postMessage
向其发送 Item ID。当这个 Worker 检索到数据并将数据发布回主线程后,您解析 XML,将结果添加到dealDetails
,然后将 XML 存储到localStorage
中。清单 6 展示了这个 Worker 脚本:details.js。清单 6. 交易细节 Worker 脚本
importScripts("common.js");onmessage = function(message){var itemId = message.data;var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){if (this.readyState == 4 && this.status == 200){postMessage({responseXml: this.responseText});}};var urlStr = generateUrl(itemId);xhr.open("GET", "proxy?url=" + escape(urlStr));xhr.send(null);}这个 Worker 脚本非常简单。您使用 Ajax 调用代理,该代理又调用 eBay Shopping API。当您收到来自代理的 XML 后,使用一个 JavaScript 对象文字(object literal)将其发送回主线程。注意,即使您能够使用来自一个 Worker 的
XMLHttpRequest
,但所有信息都将返回它的responseText
属性,而不是它的responseXml
属性。这是因为这个 Worker 脚本范围内没有 JavaScript DOM 解析器。注意,generateUrl
函数来自 common.js 文件(见 清单 7)。您使用importScripts
函数导入 common.js 文件。清单 7. Worker 导入的脚本
function generateUrl(itemId){var appId = "YOUR APP ID GOES HERE";return "http://open.api.ebay.com/shopping?callname=GetSingleItem&"+"responseencoding=XML&appid=" + appId + "&siteid=0&version=665"+"&ItemID=" + itemId;}现在,您已经知道如何(为支持 Web Workers 的浏览器)填充交易细节,我们返回 图 1 研究一下如何在应用程序中使用这种方法。注意,每笔交易旁边都有一个 Show Details 按钮,单击该按钮修改这个 UI,如 图 2 所示。
图 2. 显示的交易细节
这个 UI 将在您调用
showDetails
函数时显示。清单 8 展示了这个函数。清单 8. showDetails 函数
function showDetails(id){var el = $(id);if (el.style.display == "block"){el.style.display = "none";} else {el.style.display = "block";if (!el.innerHTML){var details = dealDetails[id];if (details){var ui = createDetailUi(details);el.appendChild(ui);} else {var itemId = id;var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){if (this.readyState == 4 &&this.status == 200){var itemDetails =parseFromXml(this.responseText);if (window.localStorage){localStorage.setItem(itemDetails.id,this.responseText);}dealDetails[id] = itemDetails;var ui = createDetailUi(itemDetails);el.appendChild(ui);}};var urlStr = generateUrl(id);xhr.open("GET", "proxy?url=" + escape(urlStr));xhr.send(null);}}}}您收到了即将显示的交易的 ID 并切换是否显示它。当该函数第一次调用时,它将检查细节是否已经存储到 dealDetails 映射中。如果浏览器支持 Web Workers,那么这些细节已经存储且它的 UI 已经创建并添加到 DOM 中。如果这些细节还没有加载,或者,如果浏览器不支持 Workers,那么您需要执行一个 Ajax 调用来加载此数据。这就是这个应用程序无论在有无 Workers 时都同样能正常工作的原因。这意味着,如果 Workers 受到支持,那么数据就已被加载且 UI 将立即响应。如果没有 Workers,UI 仍将加载,只是需要花费几秒钟时间。
结束语
对于 Web 开发人员来说,Web Workers 听起来就像一种外来的新技术。但是,如本文所述,它们是非常实用的应用程序。这对于移动 Web 应用程序来说尤其正确。这些 Workers 可用于预取数据或执行其他预先操作,从而提供一个更加实时的 UI。这对于需要通过网速可能较慢的网络加载数据的移动 Web 应用程序来说尤其正确。结合使用这种技术和缓存策略,您的应用程序的快捷反应将使您的用户感到惊喜!