Web Workers 的基本信息

简介:

问题:JavaScript 并行性

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

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

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

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

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

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

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

Web Worker 的类型

值得注意的是,规范中介绍了两种 Web Worker:专用 Worker 和 共用 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 对象传递消息的更复杂的示例。

主脚本:

复制代码
<button onclick="sayHI()">Say HI</button>
<button onclick="unknownCmd()">Send unknown command</button>
<button onclick="stop()">Stop worker</button>
<output id="result"></output>

<script>
  function sayHI() {
    worker.postMessage({'cmd': 'start', 'msg': 'Hi'});
  }

  function stop() {
    // Calling worker.terminate() from this script would also stop the worker.
    worker.postMessage({'cmd': 'stop', 'msg': 'Bye'});
  }

  function unknownCmd() {
    worker.postMessage({'cmd': 'foobard', 'msg': '???'});
  }

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

  worker.addEventListener('message', function(e) {
    document.getElementById('result').textContent = e.data;
  }, false);
</script>
复制代码

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() 方法导入外部脚本
  • 生成其他 Web Worker

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

复制代码
// Prefixed in Webkit, Chrome 12, and FF6: window.WebKitBlobBuilder, window.MozBlobBuilder
var 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 网址

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

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

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

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

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

完整示例

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

复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>

  <div id="log"></div>

  <script id="worker1" type="javascript/worker">
    // This script won't be parsed by JS engines because its type is javascript/worker.
    self.onmessage = function(e) {
      self.postMessage('msg from worker');
    };
    // Rest of your worker code goes here.
  </script>

  <script>
    function log(msg) {
      // Use a fragment: browser will only render/reflow once.
      var fragment = document.createDocumentFragment();
      fragment.appendChild(document.createTextNode(msg));
      fragment.appendChild(document.createElement('br'));

      document.querySelector("#log").appendChild(fragment);
    }

    var bb = new BlobBuilder();
    bb.append(document.querySelector('#worker1').textContent);

    // Note: window.webkitURL.createObjectURL() in Chrome 10+.
    var worker = new Worker(window.URL.createObjectURL(bb.getBlob()));
    worker.onmessage = function(e) {
      log("Received: " + e.data);
    }
    worker.postMessage(); // Start the worker.
  </script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>

  <div id="log"></div>

  <script id="worker1" type="javascript/worker">
    // This script won't be parsed by JS engines because its type is javascript/worker.
    self.onmessage = function(e) {
      self.postMessage('msg from worker');
    };
    // Rest of your worker code goes here.
  </script>

  <script>
    function log(msg) {
      // Use a fragment: browser will only render/reflow once.
      var fragment = document.createDocumentFragment();
      fragment.appendChild(document.createTextNode(msg));
      fragment.appendChild(document.createElement('br'));

      document.querySelector("#log").appendChild(fragment);
    }

    var bb = new BlobBuilder();
    bb.append(document.querySelector('#worker1').textContent);

    // Note: window.webkitURL.createObjectURL() in Chrome 10+.
    var worker = new Worker(window.URL.createObjectURL(bb.getBlob()));
    worker.onmessage = function(e) {
      log("Received: " + e.data);
    }
    worker.postMessage(); // Start the worker.
  </script>
</body>
</html>
复制代码

在我看来,这种新方法稍显清晰明了。它通过 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 上运行的:

...
复制代码
<script id="worker2" type="javascript/worker">
self.onmessage = function(e) {
  var data = e.data;

  if (data.url) {
    var url = data.url.href;
    var index = url.indexOf('index.html');
    if (index != -1) {
      url = url.substring(0, index);
    }
    importScripts(url + 'engine.js');
  }
  ...
};
</script>
<script>
  var worker = new Worker(window.URL.createObjectURL(bb.getBlob()));
  worker.postMessage({url: document.location});
</script>
复制代码

处理错误

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

复制代码
<output id="error" style="color: red;"></output>
<output id="result"></output>

<script>
  function onError(e) {
    document.getElementById('error').textContent = [
      'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join('');
  }

  function onMsg(e) {
    document.getElementById('result').textContent = e.data;
  }

  var worker = new Worker('workerWithError.js');
  worker.addEventListener('message', onMsg, false);
  worker.addEventListener('error', onError, false);
  worker.postMessage(); // Start worker without a message.
</script>
复制代码

示例: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,如需转载请自行联系原作者


相关文章
|
3月前
|
前端开发 数据安全/隐私保护
【前端web入门第二天】03 表单-下拉菜单 文本域 label标签 按钮 【附注册信息综合案例】
本文档详细介绍了HTML表单的多种元素及其用法,包括下拉菜单(`&lt;select&gt;` 和 `&lt;option&gt;`)、文本域(`&lt;textarea&gt;`)、标签解释(`&lt;label&gt;`)、各类按钮(`&lt;button&gt;`)及表单重置功能、无语义布局标签(`&lt;div&gt;` 和 `&lt;span&gt;`)以及字符实体的应用。此外,还提供了一个完整的注册信息表单案例,涵盖个人信息、教育经历和工作经历等部分,展示了如何综合运用上述元素构建实用的表单。
【前端web入门第二天】03 表单-下拉菜单 文本域 label标签 按钮 【附注册信息综合案例】
|
2月前
|
移动开发 JavaScript 前端开发
HTML5 Web Workers详解
HTML5 Web Workers 允许在后台线程中运行 JavaScript,实现复杂计算而不影响用户界面,提升应用性能。其主要特性包括并行处理、异步通信、独立作用域及多数据类型支持。通过创建和使用 Worker 文件,如 `worker.js`,可执行后台任务,并与主线程通过消息传递机制通信。适用于数据处理、图像处理、复杂计算及网络请求并行等场景。需要注意的是,Web Workers 在浏览器兼容性、安全性限制、调试及资源消耗方面需特别关注。合理利用 Web Workers 可显著增强 Web 应用的流畅度和响应速度。
|
7月前
|
弹性计算 JSON Shell
基于Web API的自动化信息收集和整理
【4月更文挑战第30天】
89 0
|
4月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
91 1
|
4月前
|
Serverless 对象存储
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
43 1
|
5月前
|
开发框架 NoSQL 前端开发
在Winform项目和Web API的.NetCore项目中使用Serilog 来记录日志信息
在Winform项目和Web API的.NetCore项目中使用Serilog 来记录日志信息
|
4月前
|
移动开发 数据挖掘 API
HTML5 中 Web Workers API 的用法
【8月更文挑战第24天】
53 0
|
5月前
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
66 1
|
5月前
|
缓存 JavaScript 前端开发
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第4天】JavaScript的Web Workers和Service Worker增强了Web性能。Web Workers处理后台多线程,减轻主线程负担,但通信有开销,受同源策略限制。Service Worker则用于离线缓存和推送通知,需管理其生命周期、更新策略,并确保安全。两者都带来了挑战,但也极大提升了用户体验。通过理解和优化,开发者能构建更高效、安全的Web应用。
138 2
|
5月前
|
缓存 前端开发 JavaScript
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第10天】在Web开发中,Web Workers和Service Worker提升性能。Workers运行后台任务,防止界面冻结。Web Workers处理计算密集型任务,Service Worker则缓存资源实现离线支持。常见问题包括通信故障、资源限制、注册错误及缓存更新。通过示例代码展示了两者用法,并强调生命周期管理和错误处理的重要性。善用这些技术,可构建高性能的Web应用。
106 0