65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5 WebWorkers 实验

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (57投票s)

2011年7月14日

CPOL

13分钟阅读

viewsIcon

182650

downloadIcon

1586

使用 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 WebWorkers 相当新,我猜它们尚未得到广泛使用。那么它们的基本思想是什么呢?坦率地说,WebWorker 是将在新线程中执行的工作单元。是的,没错,就是在浏览器中创建新线程的能力。

主机与 WebWorker 之间的通信

主机代码(创建 WebWorker 的代码)与实际的 WebWorker 之间的通信是通过类似 PostMessage 的 API 和 WebWorker 公开的几个事件处理程序来实现的。

以下是主机代码中一些典型代码的样子,请注意我们如何创建一个新的 WebWorker 并连接其两个事件:

  1. onMessage,连接到 workerResultReceiver 函数
  2. 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 插件将连接新创建的 WebWorkeronMessage()/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,以及各种其他东西。

总之,够了,希望您喜欢这篇文章。

© . All rights reserved.