HTML5 WebWorkers 实验






4.95/5 (57投票s)
使用 HTML5 WebWorkers 和自定义 jQuery 插件创建 Flickr 图片墙。
目录
引言
这篇文章是关于什么的?嗯,它是关于我以前从未做过的几件事情,即编写我自己的 jQuery 插件和使用 HTML5 WebWorker
。
对于从未听说过 jQuery 的人来说,它本质上是一个 JavaScript 库。以下是 jQuery 网站关于 jQuery 的介绍:
“jQuery 是一个快速简洁的 JavaScript 库,它简化了 HTML 文档的遍历、事件处理、动画和 AJAX 交互,以实现快速的 Web 开发。jQuery 的设计宗旨是改变您编写 JavaScript 的方式。”
-- https://jqueryjs.cn/ 于 2011 年 7 月 13 日更新
创建自己的 jQuery 插件是一个有据可查的过程,您可以在 jQuery 网站上阅读相关内容,但我在下面的部分中将详细介绍。
HTML5 WebWorker
本质上是通过 JavaScript 创建的线程;如果您是 .NET 开发者,可以将其视为 System.Threading.Tasks.Task
。它们只是在一个专用线程中执行一个工作单元。我们稍后会详细介绍。
那么,在本文中,这两个东西是如何结合使用的呢?显然,我需要一个用例。所以,我提出了这个场景,这也是本文要做的。
我想创建一个可以应用于单个元素的 jQuery 插件,该插件接受一个搜索词数组。对于每个搜索词,都会创建一个新的 HTML5 WebWorker
,它将执行 AJAX Flickr 搜索,查找与该搜索词匹配的图像。当 HTML5 WebWorker
完成时,HTML5 WebWorker
会回调到 jQuery 插件,此时将创建一个图片墙。
简而言之,这就是本文代码所做的。我们将在下面的部分中详细介绍。
编写 jQuery 插件
正如我在引言中所述,我想创建自己的 jQuery 插件。正如我之前提到的,这是一个有据可查的过程,可在 jQuery 网站链接上找到:http://docs.jquery.com/Plugins/Authoring。
我的 jQuery 插件并未实现 jQuery 网站上的所有建议。以下是它实现的功能列表:
- 背景
- 保持链式调用
- 默认值和选项
要了解这些含义,请参考 jQuery 网站链接:http://docs.jquery.com/Plugins/Authoring。
Web Workers:基本概念
HTML5 WebWorker
s 相当新,我猜它们尚未得到广泛使用。那么它们的基本思想是什么呢?坦率地说,WebWorker
是将在新线程中执行的工作单元。是的,没错,就是在浏览器中创建新线程的能力。
主机与 WebWorker 之间的通信
主机代码(创建 WebWorker
的代码)与实际的 WebWorker
之间的通信是通过类似 PostMessage
的 API 和 WebWorker
公开的几个事件处理程序来实现的。
以下是主机代码中一些典型代码的样子,请注意我们如何创建一个新的 WebWorker
并连接其两个事件:
onMessage
,连接到workerResultReceiver
函数onError
,连接到workerErrorReceiver
函数
并且使用 PostMessage
API 启动 worker,这是所有到/来自 worker 的消息都通过它进行。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// code in WebWorkwer hosting code
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var worker = new Worker("scripts/FlickrWorkerSearch.js");
worker.onmessage = workerResultReceiver;
worker.onerror = workerErrorReceiver;
worker.postMessage({ 'cmd': 'start', 'msg': settings.searchWords[i] });
function workerResultReceiver(e) {
//so something with the workers data
var result = e.Data;
}
function workerErrorReceiver(e) {
console.log("there was a problem with the WebWorker within " + e);
}
以下是典型的 WebWorker
示例:
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// code in WebWorkwer JavaScript file
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
postMessage("The worker says hello");
break;
case 'stop':
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
这个简单的例子只是将一个字符串发送回主机,说 *“Worker 问好”*。
工作单元
对于 WebWorker
而言,工作单元是一个单独的 JavaScript 文件,在构造 WebWorker
时会将其传递给它,如下所示:
var worker = new Worker("scripts/FlickrWorkerSearch.js");
我们将在本文后面看到一个典型的 worker JavaScript 文件是什么样的。
导入其他脚本
您可以通过类似于下面显示的行来导入主 WebWorker
中的其他 JavaScript 文件:
ImportScripts("scripts/SomeJavaScriptFile.js");
但请注意,您导入的 JavaScript 文件 **不得** 以任何方式修改 DOM(这意味着它们可能无法在内部使用 jQuery,因为 jQuery 使用 window(位于 DOM 中))。
注意事项
本节概述了在使用 WebWorker
时需要注意的一些事项。
无 DOM 访问
WebWorker
无法访问 DOM,因此无法导入任何访问 DOM 的脚本,例如 jQuery。这很可惜,因为 jQuery 包含一些非 DOM 相关的额外功能,在 WebWorker
中使用会很方便,但规则就是规则。
我最初以为可以通过在 WebWorker
主机中使用 DOM 并通过消息将某个对象传递给 WebWorker
来规避此规则;然而,此方法也失败了,因为对象无法被克隆。
经验法则通常是,到/来自 WebWorker
的消息必须是原始类型,如数组/字符串和其他简单类型。
无线程锁定原语
好的,我们有了运行线程的能力,但不幸的是,我们没有任何线程对象来确保这些线程之间的通信,并确保对共享数据结构的线程安全访问。这似乎是一个很大的疏忽。这意味着您的 WebWorker
及其在主机中的相关消息处理程序必须基本上是自包含的,并且不能访问任何共享数据结构。我在本文的代码中掉入了此陷阱,当时我正在填充一个公共数组,直到我意识到这可能不太明智,然后我将数组的作用域移到了 WebWorker
本地,然后一切都好了。
演示应用如何工作
这些子节将概述演示应用的工作原理。
如何运行演示
演示是一个 VS2010 解决方案,很方便,但如果您没有 VS2010,请不要担心,只需打开 Windows Explorer 并找到文件“Index.html”,右键单击并选择 Firefox 打开;是的,没错,我需要您使用特定的浏览器(稍后详细介绍)。
浏览器支持
WebWorker
(s) 支持以下浏览器:
浏览器 | 支持 WebWorker 的选项 |
---|---|
Chrome | v3 或更高版本 |
Firefox | v3.5 或更高版本 |
IE | v9 或更高版本 |
Opera | 10.6 或更高版本 |
Safari | v4 或更高版本 |
从上表可以看出,对 WebWorker
(s) 的支持相当不错。尽管对于本文,我实际上并不关心跨浏览器兼容性,也从未花时间担心过,是的,您可以为此抱怨,但这不会改变任何事情。
我这样做的原因如下:
- 我想写关于
WebWorker
(s) 的文章,而不是关于如何让事物在不同浏览器中正常工作。 - 我根本没有足够的时间来关心这个问题。
- 我认为本文很好地演示了使用
WebWorker
(s) 的关键概念,这正是我所追求的。
因此,我唯一知道可以正常工作的浏览器是我偶尔进行 Web 编程时最常使用的浏览器,即 Firefox(v3.5 或更高版本)。其他一些浏览器无法正常工作的原因为,我使用的免费 图片库 jQuery 插件不喜欢返回不同尺寸的图像。Firefox 似乎对此处理得很好,但 Chrome 则不行。想想看。正如我所说,本文的重点是 WebWorker
(s),而不是修改他人 图片库 jQuery 插件,所以抱歉,事实就是如此。
HTML 部分
参见 JavaScript 文件:Index.html
大部分 HTML 来自本文使用的免费 图片库 jQuery 插件。我唯一更改的是包含我的其他 JavaScript 文件,并通过使用我自己的 自定义 jQuery 插件(我们将在下一节讨论)来创建动态内容,用于 DIV class="container"
元素。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Experimenting With HTML5 WebWorkers</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="description" content="Simple Jquery/Html5
WebWorkers demo to build image wall based on WebWorker Flickr search" />
<meta name="keywords" content="jquery, html5, full screen, webworker, flickr" />
<link rel="stylesheet" href="css/style.css"
type="text/css" media="screen" />
<script type="text/javascript" src="scripts/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="scripts/jquery.easing.1.3.js"></script>
<script type="text/javascript" src="scripts/FlickrWall.js"></script>
<script type="text/javascript" src="scripts/FullPageImageGallery.js"></script>
</head>
<body>
<div id="webWorkAvailability" style="display :none;" >
<p>WEB WORKERS ARE NOT AVAILABLE</p>
</div>
<div id="fp_gallery" class="fp_gallery">
<img src="images/1.jpg" alt=""
class="fp_preview" style="display: none;" />
<div class="fp_overlay"></div>
<div id="fp_loading" class="fp_loading">
</div>
<div id="fp_next" class="fp_next">
</div>
<div id="fp_prev" class="fp_prev">
</div>
<div id="outer_container">
<div id="thumbScroller">
<div class="container">
</div>
</div>
</div>
<div id="fp_thumbtoggle"
class="fp_thumbtoggle">View Thumbs</div>
</div>
<div>
</div>
</body>
</html>
基本上,此部分通过我们将在下一节讨论的 自定义 jQuery 插件动态更新。
<div class="container">
</div>
正如我所说,大部分 HTML 来自本文使用的免费 图片库 jQuery 插件。
自定义 jQuery 插件部分
参见 JavaScript 文件:FlickrWall.js
我编写的 jQuery 插件的工作非常简单。它应用于特定元素,其中 jQuery 插件接受一个搜索词数组。对于每个搜索词,都会创建一个新的 WebWorker
,它将执行 AJAX Flickr 搜索,查找与该搜索词匹配的图像。对于每个创建的 WebWorker
,我的自定义 jQuery 插件将连接新创建的 WebWorker
的 onMessage()
/onError
事件。当 WebWorker
完成时,WebWorker
会回调到 jQuery 插件,此时将创建一个图片墙。
//------------------------------------------------------------------------
//
// This is a simple jQuery plugin that can be applied to a single element,
// where the jQuery plugin would accept an array of search terms.
// For each of the search terms a new Html5 WebWorker is spawned that will
// do a Ajax flickr search for images that match that search term.
// When the Html5 WebWorker completes, the Html5 WebWorker calls back into the
// jQuery plugin, at which point an image wall is created.
//
//------------------------------------------------------------------------
(function ($) {
$.fn.FlickrImageWall = function (options) {
var wwsAreOk = false;
var workersCompleted = 0;
var src = "";
var workerArray = new Array();
var imagesSoFar = 0;
var maxImages = 15;
//Check for WebWorker availability
if (Supports_web_workers()) {
$(".webWorkAvailability").hide();
wwsAreOk = true;
} //Assume these setting values, unless new values are
//supplied by the caller of this plugin
var settings = {
'searchWords': ['dog', 'cat', 'shark']
};
//The is the call back from the WebWorker that is called
//when the worker sends a message back to this hosting jQuery plugin
//via the PostMessage API
function workerResultReceiver(e) {
//Each worker must have its only local data,
//cant modified unsafe global fields, as they are not thread safe.
var workerImages = new Array();
var jsonData = $.parseJSON(e.data);
var src;
for (var i = 0; i < 5; i++) {
src = "http://farm" + jsonData.photos.photo[i].farm +
".static.flickr.com/" + jsonData.photos.photo[i].server +
"/" + jsonData.photos.photo[i].id + "_" +
jsonData.photos.photo[i].secret + "_b.jpg";
workerImages.push(src);
}
PopulateWall(workerImages);
//check to see if all the web workers have completed yet, and if stop all
//workers by sending a new stop message
//Pretty sure the access to imagesSoFar is not thread safe, but had no choice
imagesSoFar = imagesSoFar + 1;
if (imagesSoFar == workerArray.length) {
for (var j = 0; j < workerArray.length; j++) {
workerArray[j].postMessage({ 'cmd': 'stop', 'msg': null });
}
}
}
//The is the call back from the WebWorker that is called
//when the worker sends a error message back
function workerErrorReceiver(e) {
console.log("there was a problem with the WebWorker " + e);
}
//The jQuery meat, this is what will be run against the selected
//element set that this jQuery plugin is applied to
return this.each(function () {
if (options) {
$.extend(settings, options);
} //allows chaining of jQuery plugins
var $this = $(this);
//if webworkers are supported
if (wwsAreOk) {
//for each keyword, need to start a new web worker off that will search Flickr
for (i = 0; i < settings.searchWords.length; i++) {
var worker = new Worker("scripts/FlickrWorkerSearch.js");
worker.onmessage = workerResultReceiver;
worker.onerror = workerErrorReceiver;
worker.postMessage({ 'cmd': 'start', 'msg': settings.searchWords[i] });
workerArray.push(worker);
}
}
});
//populate the wall by building up dynamic content based on the Ajax fetch Flickr
//data. Finally make a call to the ImageGallery
//jQuery plugin, via the CreateWall() function
function PopulateWall(images) {
var fullcontent = "";
for (var j = 0; j < 5; j++) {
var imagName = images[j];
var fullstring = "<div class=\"content\"><div><a" +
" href=\"#\"><img src=\"" +
imagName + "\" alt=\"" + imagName +
"\" class=\"thumb\" /></a></div></div>";
fullcontent = fullcontent + fullstring;
}
$(".container").append(fullcontent);
CreateWall();
}
//checks for WebWorker support
function Supports_web_workers() {
return !!window.Worker;
}
};
})(jQuery);
$(document).ready(function () {
//hook up FlickrImageWall jQuery plugin to the single
//element that will receive the dynamic images being
//added to it
$(".container").FlickrImageWall({ 'searchWords': ['bikini', 'tatoo','water']
});
});
我应该指出,调用 CreateWall()
会调用一个第三方 jQuery 插件,该插件将在本文的 图片库部分中进行讨论。
WebWorker 部分
参见 JavaScript 文件:FlickrWorkerSearch.js
如您现在所知,WebWorker
(s) 必须使用标准的 JavaScript 创建。您现在也知道 WebWorker
不能修改 DOM。我已经说过,我的问题领域决定每个 WebWorker
都将对 Flickr API 进行 AJAX 调用。这如何实现?嗯,我们只需进行手动 AJAX 调用。
以下是 “FlickrWorkerSearch.js” 这个 WebWorker
JavaScript 文件中的全部代码,它在我的自定义 Query 插件 “FlickrWall.js” 中用于创建新的 WebWorker
(s) 来执行搜索。
//Does the Ajax flickr search based on a given url and then
//either posts the Ajax response or null using the PostMessage API
function GetData(url) {
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
postMessage(xhr.responseText);
}
}
};
xhr.send(null);
} catch (e) {
postMessage(null);
}
}
//Adds a listener to the message event
//This is the main message pump for the PostMessage API for the WebWorker,
//this deals with all the different message types that the worker/host can use
//to communicate
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
var url = "http://api.flickr.com/services/rest/?method=flickr.photos.search" +
"&api_key=FLICKR_API_KEY&tags=" + data.msg +
"&safe_search=1&per_page=20&format=json&nojsoncallback=1";
GetData(url);
break;
case 'stop':
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
请注意
我好心将我的 Flickr API 密钥保留在里面,以便您获得一个功能齐全的演示,但请不要复制它或更改代码以大量请求 Flickr,或做任何可能导致 Flickr 取消我的开发者密钥的事情。基本上,请友好地使用。
图片库部分
参见 JavaScript 文件:FullPageImageGallery.js 和 jquery.easing.1.3.js
这可能是我演示中最漂亮的部分,遗憾的是,我对此不负任何责任,这只是互联网上众多免费 jQuery 图片库插件之一。
原始来源是免费提供的:http://www.ajaxshake.com/plugin/EN/781/0a2daf19/jQuery-full-page-image-gallery-full-gallery.html,其中此 jQuery 插件(请参阅 “FullPageImageGallery.js”)也使用了 jQuery 缓动插件,即 “jquery.easing.1.3.js” 文件。
我确实遇到了一些时序问题,这是由于 WebWorker
(s) 本质上是新的执行线程,而图片库的设计者显然没有考虑到这一点。这导致我重构了图片库代码,使其可以在我的自定义 jQuery 插件中接收到 WebWorker
发送的消息时进行调用。这些更改很小,而且在很大程度上与本文无关。但我确实必须想办法动态地操作窗口元素(这是图片库 jQuery 插件的目标)的 DOM。不过,这与本文的范围关系不大,请放心,我必须进行一些 DOM 操作才能使用此 jQuery 插件创建动态墙。
不过,我不得不说,我选择这个特定的图片库 jQuery 插件的原因是它真的很酷。
但等等,浏览器怪异
不过,这个免费的 jQuery 插件有一个问题,那就是它假定所有图像都具有特定大小并且是正方形(这显然是我无法保证的,因为图像来自 Flickr)。Firefox 似乎对此处理得很好,但其他浏览器(如 Chrome)则不行。由于本文的重点是 WebWorker
(s),而不是修改他人 图片库 jQuery 插件,所以抱歉,事实就是如此。
总之,除了浏览器问题之外,一旦它填充了来自 WebWorker
(s) AJAX 调用的图像,它就会使用动态创建的内容,如下所示,用户可以通过鼠标左右滚动,然后单击图像(单击下面的图像查看更大的版本)。
一旦单击图像,它就会扩展到全屏,此时用户可以使用左右按钮滚动全屏图像,或返回缩略图。如下所示(单击下面的图像查看更大的版本)。
总而言之,我对这个免费的 jQuery 插件非常满意。
老实说,我认为微软可以从 jQuery/社区的贡献中学到很多东西。想象一下,如果人们可以像扩展 jQuery 一样扩展 WPF/Silverlight 并将其发布出去。我将举一个使用动画 jQuery 库的图片库的例子。所以让我们更深入地看看它。我编写了一个 jQuery 插件,它调用了一个免费的 jQuery 图片库插件,而后者又依赖于另一个免费的 jQuery 动画插件。
如果我们身处微软的世界(我经常身处其中),我们必须在 CodePlex 或其他地方搜索 Silverlight/WPF 控件,我们可能会找到被 10-50 人下载过的东西,或者我们只能等待微软在 .NET(x) 中为我们准备什么。
相比之下,当您搜索 jQuery 的图片库时,有成百上千种选择。它的社区性质更强,因为它不像扩展那么封闭,这一点很明显。
然而,在如此开放的环境中,您会遇到一些看起来不错但最终未经测试、粗糙且完全无法使用的东西;但是,如果您愿意在互联网上进行搜索,还是有一些珍宝的。
我个人喜欢互联网,所以我愿意去发掘那些珍宝。
结论
就这样。我知道我并不以在 Web 社区的贡献而闻名,而且说实话,我认为这也不会改变,但确保您了解如何在流行技术中做事总是值得的。我确实认为 HTML5 将变得非常流行,但在我看来,其中有一些非常愚蠢的需要修复的地方,例如我在本文中发现的。
例如,现在支持额外的线程(是的,通过 WebWorker
(s)),但却没有原生的 JavaScript 机制来实现线程安全,这很奇怪,您知道像 lock(..)
以及我们在 .NET/Java 或大多数静态类型语言中拥有的所有其他锁定原语,甚至大多数语言都是如此。
我确实理解 WebWorker
不允许访问 DOM 的想法,嗯,我有点理解。如果允许它会更好,但它必须具有线程亲和性,并且 WebWorker
必须提供某种线程亲和性对象。Windows 一直使用这个模型,Java 对线程亲和性出奇地宽容。我不在乎采用哪种方法,但 “禁止 DOM” JavaScript 交互的 blanket 规则在 WebWorker
中非常受限;您知道 jQuery 包含很多好东西,我知道它主要是一个 DOM 操作 API,但还有很多其他非常有用的功能,例如执行 AJAX 调用、解析 JSON,以及各种其他东西。
总之,够了,希望您喜欢这篇文章。