博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Web Workers 的基本信息
阅读量:6671 次
发布时间:2019-06-25

本文共 6254 字,大约阅读时间需要 20 分钟。

问题:JavaScript 并行性

要将有趣的应用(例如从侧重服务器端的实施)移植到客户端 JavaScript,存在很多制约瓶颈。其中包括浏览器兼容性、静态类型、可访问性和性能。幸运的是,随着浏览器供应商快速提高 JavaScript 引擎的速度,性能已不再是瓶颈。

仍在阻碍 JavaScript 的实际上是语言本身。JavaScript 属于单线程环境,也就是说无法同时运行多个脚本。例如,假设有一个网站,它需要处理 UI 事件,查询并处理大量 API 数据以及操作 DOM。这很常见,不是吗?遗憾的是,由于受到浏览器 JavaScript 运行时的限制,所有这些操作都无法同时进行。脚本是在单个线程中执行的。

开发人员会使用 setTimeout()setInterval()XMLHttpRequest 和事件处理程序等技术模拟“并行”。所有这些功能确实都是异步运行的,但没有阻碍未必就意味着并行。系统会在生成当前执行脚本后处理异步事件。好消息是,HTML5 为我们提供了优于这些技巧的技术。

Web Worker 简介:为 JavaScript 引入线程技术

 规范定义了在网络应用中生成背景脚本的 API。您可以通过 Web Worker 执行一些操作,例如触发长时间运行的脚本以处理计算密集型任务,同时却不会阻碍 UI 或其他脚本处理用户互动。这有助于解决令人讨厌的“无响应脚本”对话框(大家都有些爱上它了吧)问题:

无响应脚本对话框 常见无响应脚本对话框。

Worker 利用类似线程的消息传递实现并行。这非常适合您确保对 UI 的刷新、性能以及对用户的响应。

Web Worker 的类型

值得注意的是,中介绍了两种 Web Worker: 和 。本文只涉及专用 Worker,并在全文中将其称为“Web Worker”或“Worker”。

使用入门

Web Worker 在独立线程中运行。因此,它们执行的代码需要保存在一个单独的文件中。但在保存代码前,我们要先在您的主网页上创建新的 Worker 对象。构造函数采用 Worker 脚本的名称:

var worker = new Worker('task.js');

如果指定的异步下载文件存在,浏览器就会生成新的 Worker 线程。在完全下载并执行文件之前,系统不会生成 Worker。如果指向您 Worker 的路径返回 404,Worker 就会在不显示任何提示的情况下失败。

创建 Worker 之后,通过调用 postMessage() 方法启动:

worker.postMessage(); // Start the worker.
通过消息传递与 Worker 通信

Worker 与其父网页之间的通信是通过事件模型和 postMessage() 方法实现的。postMessage() 可以接受字符串或 JSON 对象作为单个参数,具体取决于您的浏览器/版本。新式浏览器的最新版支持传递 JSON 对象。

以下示例使用字符串将“Hello World”传递给了 doWork.js 中的 Worker。Worker 直接返回了传递给它的消息。

主脚本:

var worker = new Worker('doWork.js');worker.addEventListener('message', function(e) {  console.log('Worker said: ', e.data);}, false);worker.postMessage('Hello World'); // Send data to our worker.doWork.js (Worker):self.addEventListener('message', function(e) {  self.postMessage(e.data);}, false);
 

在主网页中调用 postMessage() 时,我们的 Worker 通过定义 message 事件的 onmessage 处理程序来处理消息。您可以在 Event.data 中访问消息有效负载(此示例中为“Hello World”)。虽然这个特殊的示例并不精彩,但它说明 postMessage() 也是您将数据传回主线程的一种方法。很方便!

在主网页和 Worker 之间传递的消息是复制而不是共享的。例如,下一示例中 JSON 消息的“msg”属性在两个位置中均可访问。即使对象运行在单独的专用空间中,系统似乎也会将其直接传递给 Worker。实际发生的情况是,系统将对象传递给 Worker 后,会将其序列化,随后在另一端解取消序列化。由于网页和 Worker 并不共享同一实例,因此每次传递时都要进行复制。大部分浏览器通过在任一端上对值进行自动 JSON 编码/解码来实施此功能。

下面是一个使用 JSON 对象传递消息的更复杂的示例。

主脚本:

doWork2.js:

self.addEventListener('message', function(e) {  var data = e.data;  switch (data.cmd) {    case 'start':      self.postMessage('WORKER STARTED: ' + data.msg);      break;    case 'stop':      self.postMessage('WORKER STOPPED: ' + data.msg + '. (buttons will no longer work)');      self.close(); // Terminates the worker.      break;    default:      self.postMessage('Unknown command: ' + data.msg);  };}, false);

请注意:停止 Worker 的方法有两种:在主网页中调用 worker terminate(),或在 Worker 本身内部调用 self.close()

示例:运行此 Worker!

打招呼发送未知命令停止 Worker

Worker 环境

Worker 作用域

就 Worker 来说,self 和 this 指的都是 Worker 的全局作用域。因此,上一示例也可以写成:

addEventListener('message', function(e) {  var data = e.data;  switch (data.cmd) {    case 'start':      postMessage('WORKER STARTED: ' + data.msg);      break;    case 'stop':  ...}, false);

或者,您可以直接设置 onmessage 事件处理程序(虽然 JavaScript 高手们总是会推荐 addEventListener)。

onmessage = function(e) {  var data = e.data;  ...};
适用于 Worker 的功能

由于 Web Worker 的多线程行为,所以它们只能使用 JavaScript 功能的子集:

  • navigator 对象
  • location 对象(只读)
  • XMLHttpRequest
  • setTimeout()/clearTimeout() 和 setInterval()/clearInterval()
  • 使用 importScripts() 方法导入外部脚本

Worker 无法使用:

  • DOM(非线程安全)
  • window 对象
  • document 对象
  • parent 对象
加载外部脚本

您可以通过 importScripts() 函数将外部脚本文件或库加载到 Worker 中。该方法采用零个或多个字符串表示要导入的资源的文件名。

此示例将 script1.js 和 script2.js 加载到了 Worker 中:

worker.js:

importScripts('script1.js');importScripts('script2.js');

也可以写成单个导入语句:

importScripts('script1.js', 'script2.js');
子 Worker

Worker 可以生成子 Worker。这对于在运行时进一步拆分大任务来说非常重要。但是,子 Worker 还有几点注意事项:

  • 子 Worker 必须托管在与父网页相同的来源中。
  • 子 Worker 中的 URI 应相对于父 Worker 的位置进行解析(与主网页不同)。

请注意,大部分浏览器会为每个 Worker 生成单独的进程。在您开始生成 Worker 场之前,请注意不要占用太多的用户系统资源。这样做的一个原因是,在主网页和 Worker 之间传递的消息是复制而不是共享的。请参阅。

有关子 Worker 生成方法的示例,请参阅规范中的。

内嵌 Worker

如果您想即时创建 Worker 脚本,或者在不创建单独 Worker 文件的情况下创建独立网页,那该怎么做呢?在新  界面中,您可以创建 BlobBuilder 并以字符串形式附上 Worker 代码,从而在与主逻辑相同的 HTML 文件中“内嵌”Worker:

// Prefixed in Webkit, Chrome 12, and FF6: window.WebKitBlobBuilder, window.MozBlobBuildervar bb = new BlobBuilder();bb.append("onmessage = function(e) { postMessage('msg from worker'); }");// Obtain a blob URL reference to our worker 'file'.// Note: window.webkitURL.createObjectURL() in Chrome 10+.var blobURL = window.URL.createObjectURL(bb.getBlob());var worker = new Worker(blobURL);worker.onmessage = function(e) {  // e.data == 'msg from worker'};worker.postMessage(); // Start the worker.
Blob 网址

对  的调用十分奇妙。此方法创建了一个简单的网址字符串,该字符串可用于 DOM File 或 Blob 对象中存储的参考数据。例如:

blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1

Blob 网址是唯一的,且只要应用存在,该网址就会一直有效(例如直到卸载 document 为止)。如果您要创建很多 Blob 网址,最好发布不再需要的参考资料。您可以通过将 Blob 网址传递给 来明确发布该网址:

window.URL.revokeObjectURL(blobURL); // window.webkitURL.createObjectURL() in Chrome 10+.

在 Chrome 浏览器中,有一个很实用的页面可供您查看创建的所有 Blob 网址:chrome://blob-internals/

完整示例

再进行一个步骤,我们就会清楚如何将 Worker 的 JavaScript 代码内嵌在网页中了。此技术使用 <script> 标签定义 Worker:

  

在我看来,这种新方法稍显清晰明了。它通过 id="worker1" 和 type='javascript/worker' 定义脚本标记(这样浏览器就不会解析 JavaScript 了)。系统会使用 document.querySelector('#worker1').textContent 以字符串形式提取该代码并将其传递给 BlobBu lder.append()

加载外部脚本

在使用这些技术内嵌 Worker 代码时,importScripts() 只会在您提供绝对 URI 的情况下生效。如果您尝试传递相对 URI,浏览器就会提示出现安全错误。原因:系统会通过 blob: 前缀解析 Worker(现在通过 Blob 网址创建),而您的应用会通过其他(可能为 http://)方案运行。因此,失败原因在于跨源限制。

在内嵌 Worker 中利用 importScripts() 的一种方法是,通过将相关网址传递给内嵌 Worker 并手动构建绝对网址来“导入”运行您主脚本的当前网址。这可以确保外部脚本是从同一来源导入的。假设您的主应用是在 http://example.com/index.html 上运行的:

...

处理错误

与任何 JavaScript 逻辑一样,您需要处理 Web Worker 中出现的任何错误。如果在执行 Worker 时出现错误,就会触发 ErrorEvent。相关界面中包含用于找出错误内容的三个实用属性:filename - 导致错误的 Worker 脚本的名称;lineno - 出现错误的行号;以及 message - 有关错误的实用说明。以下示例设置了 onerror 事件处理程序以便打印错误内容:

示例:workerWithError.js 尝试执行 1/x,其中 x 未定义。

运行

workerWithError.js:

self.addEventListener('message', function(e) {  postMessage(1/x); // Intentional error.};

安全说明

本地访问限制

由于 Google Chrome 浏览器的安全限制,Worker 无法在最新版浏览器中本地运行(例如通过 file://),且会在不显示任何提示的情况下失败!要通过 file:// 方案运行您的应用,请使用 --allow-file-access-from-files 标记设置来运行 Chrome 浏览器。请注意:不推荐使用此标记设置来运行您的主浏览器。此标记设置仅供测试用,请勿用于常规浏览。

其他浏览器不存在相同的限制。

同源注意事项

Worker 脚本必须是将相同方案作为调用网页的外部文件。因此,您无法通过 data: 网址或 javascript: 网址加载脚本,且 https: 网页无法启动以 http: 网址开头的 Worker 脚本。

本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/p/3539222.html,如需转载请自行联系原作者

你可能感兴趣的文章