Web Worker

概述

Web Worker的目的,就是为JavaScript创造多线程环境,允许主线程将一些任务分配给子线程。在主线程运行的同时,子线程在后台运行,两者互不干扰。等到子线程完成计算任务,再把结果返回给主线程。因此,每一个子线程就好像一个“工人”(worker),默默地完成自己的工作。这样做的好处是,一些高计算量或高延迟的工作,被worker线程负担了,所以主进程(通常是UI进程)就会很流畅,不会被阻塞或拖慢。

Worker线程分成好几种。
+ 普通的Worker:只能与创造它们的主进程通信。
+ Shared Worker:能被所有同源的进程获取(比如来自不同的浏览器窗口、iframe窗口和其他Shared worker),它们必须通过一个端口通信。
+ ServiceWorker:实际上是一个在网络应用与浏览器或网络层之间的代理层。它可以拦截网络请求,使得离线访问成为可能。

Web Worker有以下几个特点:
+ 同域限制。子线程加载的脚本文件,必须与主线程的脚本文件在同一个域。
+ DOM限制。子线程所在的全局对象,与主进程不一样,它无法读取网页的DOM对象,即document、window、parent这些对象,子线程都无法得到。(但是,navigator对象和location对象可以获得。)
+ 脚本限制。子线程无法读取网页的全局变量和函数,也不能执行alert和confirm方法,不过可以执行setInterval和setTimeout,以及使用XMLHttpRequest对象发出AJAX请求。
+ 文件限制。子线程无法读取本地文件,即子线程无法打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

使用之前,检查浏览器是否支持这个API。

if (window.Worker) {
  // 支持
} else {
  // 不支持
}

新建和启动子线程

主线程采用new命令,调用Worker构造函数,可以新建一个子线程。

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

Worker构造函数的参数是一个脚本文件,这个文件就是子线程所要完成的任务,上面代码中是work.js。由于子线程不能读取本地文件系统,所以这个脚本文件必须来自网络端。如果下载没有成功,比如出现404错误,这个子线程就会默默地失败。

子线程新建之后,并没有启动,必需等待主线程调用postMessage方法,即发出信号之后才会启动。postMessage方法的参数,就是主线程传给子线程的信号。它可以是一个字符串,也可以是一个对象。

worker.postMessage("Hello World");
worker.postMessage({method: 'echo', args: ['Work']});

只要符合父线程的同源政策,Worker线程自己也能新建Worker线程。Worker线程可以使用XMLHttpRequest进行网络I/O,但是XMLHttpRequest对象的responseXML和channel属性总是返回null。

子线程的事件监听

在子线程内,必须有一个回调函数,监听message事件。

/* File: work.js */

self.addEventListener('message', function(e) {
  self.postMessage('You said: ' + e.data);
}, false);

self代表子线程自身,self.addEventListener表示对子线程的message事件指定回调函数(直接指定onmessage属性的值也可)。回调函数的参数是一个事件对象,它的data属性包含主线程发来的信号。self.postMessage则表示,子线程向主线程发送一个信号。

根据主线程发来的不同的信号值,子线程可以调用不同的方法。

/* File: work.js */

self.onmessage = function(event) {
  var method = event.data.method;
  var args = event.data.args;

  var reply = doSomething(args);
  self.postMessage({method: method, reply: reply});
};

主线程的事件监听

主线程也必须指定message事件的回调函数,监听子线程发来的信号。

/* File: main.js */

worker.addEventListener('message', function(e) {
    console.log(e.data);
}, false);

错误处理

主线程可以监听子线程是否发生错误。如果发生错误,会触发主线程的error事件。

worker.onerror(function(event) {
  console.log(event);
});

// or

worker.addEventListener('error', function(event) {
  console.log(event);
});

关闭子线程

使用完毕之后,为了节省系统资源,我们必须在主线程调用terminate方法,手动关闭子线程。

worker.terminate();

也可以子线程内部关闭自身

self.close();

主线程与子线程的数据通信

前面说过,主线程与子线程之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,子线程对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给子线程,后者再将它还原。

主线程与子线程之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等对象,也可以在线程之间发送。

但是,用拷贝方式发送二进制数据,会造成性能问题。比如,主线程向子线程发送一个500MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects

如果要使用该方法,postMessage方法的最后一个参数必须是一个数组,用来指定前面发送的哪些值可以被转移给子线程。

worker.postMessage(arrayBuffer, [arrayBuffer]);
window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);

同页面的Web Worker

通常情况下,子线程载入的是一个单独的JavaScript文件,但是也可以载入与主线程在同一个网页的代码。假设网页代码如下:

<!DOCTYPE html>
    <body>
        <script id="worker" type="app/worker">

            addEventListener('message', function() {
                postMessage('Im reading Tech.pro');
            }, false);
        </script>
    </body>
</html>

我们可以读取页面中的script,用worker来处理。

var blob = new Blob([document.querySelector('#worker').textContent]);

把代码当作二进制对象读取,所以使用Blob接口。然后,这个二进制对象转为URL,再通过这个URL创建worker。

var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

部署事件监听代码。

worker.addEventListener('message', function(e) {
   console.log(e.data);
}, false);

最后,启动worker。

worker.postMessage('');

整个页面的代码如下:

<!DOCTYPE html>
<body>
  <script id="worker" type="app/worker">
    addEventListener('message', function() {
      postMessage('Work done!');
    }, false);
   </script>

  <script>
    (function() {
      var blob = new Blob([document.querySelector('#worker').textContent]);
      var url = window.URL.createObjectURL(blob);
      var worker = new Worker(url);

      worker.addEventListener('message', function(e) {
        console.log(e.data);
      }, false);

      worker.postMessage('');
    })();
  </script>
</body>
</html>

可以看到,主线程和子线程的代码都在同一个网页上面。

上面所讲的Web Worker都是专属于某个网页的,当该网页关闭,worker就自动结束。除此之外,还有一种共享式的Web Worker,允许多个浏览器窗口共享同一个worker,只有当所有网口关闭,它才会结束。这种共享式的Worker用SharedWorker对象来建立,因为适用场合不多,这里就省略了。

<think>好的,我现在需要处理用户关于WebWorker的问题。用户想了解如何在JavaScript中使用WebWorker,或者解决常见的相关问题。首先,我得回忆一下WebWorker的基本概念和用法,确保信息准确。 首先,WebWorker允许在后台线程运行脚本,避免阻塞主线程。这点很重要,尤其是在处理复杂计算时。我需要分步骤说明如何创建和使用WebWorker,包括实例化Worker对象、消息传递和终止Worker。然后,可能还要提到同源策略的限制,以及如何应对。 接下来是常见问题及解决方法。比如DOM操作的问题,因为WebWorker不能访问DOM,所以需要提醒用户通过消息传递让主线程处理。然后,错误处理,比如监听error事件。加载外部脚本的问题,可能需要使用importScripts方法。还有兼容性检查,避免在不支持的环境中使用。 然后,性能优化方面,比如避免频繁通信,因为序列化和反序列化数据有开销。内存管理方面,及时终止Worker防止内存泄漏。还要提到共享内存和SharedArrayBuffer,但需要注意线程安全。 最后,生成相关问题,帮助用户进一步学习。比如应用场景、数据通信方法、替代方案等。需要确保问题覆盖用户可能关心的其他方面。 在回答过程中,要遵循系统指令,使用正确的中文,数学公式用$...$,独立公式用$$...$$。不过这个问题可能涉及代码较多,但用户提供的引用是关于Puppeteer的,可能和WebWorker无关,但需要检查是否有必要引用。不过用户给的引用[^1]是关于Chromium命令行参数的,可能不相关,但系统要求引用标识,所以可能需要找到合适的地方插入。不过当前回答可能没有直接引用到相关内容,可能需要忽略或寻找相关点。不过根据用户提供的引用内容,可能无法直接关联到WebWorker,所以可能不需要引用,但系统指令要求引用标识,可能需要调整。或者用户可能误放了引用,需要按实际情况处理。这里可能需要忽略该引用,或者如果有相关内容的话才添加。 检查是否有必要引用用户提供的链接,可能不需要,但根据系统指令,必须在回答中引用的段落末尾添加引用标识。所以需要找到合适的位置添加。比如在提到兼容性时,可能可以提到某些浏览器设置可能需要调整,类似引用中的Chromium参数,但可能比较牵强。或者可能用户提供的引用是示例,实际回答中需要引用其他内容,但用户没有提供其他资料,所以可能无法正确引用。这种情况下,可能需要忽略引用,但系统指令要求必须添加。这里可能需要灵活处理,或者在回答末尾添加引用说明,但用户提供的引用内容是关于Puppeteer的,可能无法关联。可能存在矛盾,需要按照实际情况处理。或许用户希望引用的内容是关于WebWorker的,但实际提供的引用是关于Chromium命令行开关的,可能是个错误。这种情况下,可能需要按照系统指令,在回答中的某个段落末尾添加引用标识,即使内容不相关。例如在提到兼容性检查时,可以添加引用,尽管内容不匹配。或者可能用户提供的引用有误,需要按实际情况处理。或者可能系统示例中的引用是示例,实际回答中需要根据用户提供的引用添加。这里可能需要将引用放在某个相关的位置,比如在讨论WebWorker的兼容性或浏览器设置时,引用提供的链接,说明如何调整浏览器参数,但可能关联性不大。例如,在提到某些浏览器可能需要特定设置才能使用WebWorker,可以添加引用,但用户提供的引用是关于忽略--enable-automation参数的解决方案。这可能不太相关,但根据系统指令,必须添加引用,所以可能需要找到一个相关点。或者可能在讨论WebWorker的调试时,提到使用开发者工具,可能需要引用相关资源,但用户提供的引用是关于Chromium命令行参数的,可能涉及启动参数,比如在调试时可能需要禁用某些安全策略,可以参考该链接中的参数设置。例如,如果WebWorker在本地文件系统中运行时遇到问题(由于同源策略),可能需要通过命令行参数启动浏览器,这时可以引用该链接。因此,在讨论同源策略限制时,可以添加引用,例如提到跨域问题可能需要调整浏览器启动参数,具体可参考相关资源[^1]。这样就能合理插入引用。 综上,回答结构大致如下: 1. WebWorker基本用法:创建、通信、终止。 2. 常见问题及解决方法:DOM操作、错误处理、外部脚本加载、兼容性。 3. 性能优化与内存管理。 4. 相关问题生成。 5. 在讨论同源策略或浏览器设置时,插入引用。</think>### WebWorker的用法与常见问题解决 #### 1. WebWorker基本用法 WebWorker允许在后台线程执行脚本,避免阻塞主线程。使用步骤如下: **a. 创建Worker文件** 新建一个独立的JavaScript文件(如`worker.js`),编写需要在后台运行的代码: ```javascript // worker.js self.onmessage = function(e) { const result = e.data * 2; // 示例:简单计算 self.postMessage(result); }; ``` **b. 主线程中实例化Worker** 在主线程中通过`new Worker()`创建Worker实例,并定义消息接收和错误处理: ```javascript // 主线程代码 const worker = new Worker('worker.js'); // 发送数据到Worker worker.postMessage(10); // 接收Worker返回的结果 worker.onmessage = function(e) { console.log('结果:', e.data); // 输出:20 }; // 错误处理 worker.onerror = function(e) { console.error('Worker错误:', e.message); }; // 终止Worker // worker.terminate(); ``` **注意事项**: - **同源策略**:Worker脚本必须与主线程同源,否则会触发安全错误。若需跨域加载,可通过`Blob`或调整服务器CORS策略实现。 - **作用域限制**:Worker无法访问`DOM`、`window`或`document`对象。 --- #### 2. 常见问题与解决方案 **问题1:Worker中无法操作DOM** **原因**:Worker运行在独立线程,与主线程环境隔离。 **解决**:通过`postMessage`将计算结果传回主线程,由主线程更新DOM。 --- **问题2:Worker脚本加载失败** **错误示例**:`Uncaught DOMException: Failed to construct 'Worker'` **解决**: 1. 检查文件路径是否正确。 2. 确保服务器配置允许加载Worker脚本(如MIME类型为`application/javascript`)。 --- **问题3:数据序列化限制** **示例错误**:传递无法序列化的对象(如函数)会触发`DataCloneError`。 **解决**:仅传递简单数据(如JSON、`ArrayBuffer`)或使用结构化克隆算法支持的类型。 --- **问题4:兼容性问题** **场景**:低版本浏览器(如IE)不支持WebWorker。 **解决**: ```javascript if (typeof Worker !== 'undefined') { // 使用WebWorker } else { // 降级方案:在主线程处理或提示不支持 } ``` --- #### 3. 性能优化与内存管理 - **减少通信频率**:批量传输数据,避免频繁`postMessage`调用。 - **终止闲置Worker**:调用`worker.terminate()`释放资源。 - **使用共享内存**:通过`SharedArrayBuffer`实现高效数据共享(需注意线程安全)。 --- #### 4. 调试技巧 - 在Chrome开发者工具中,通过`Sources > Threads`查看Worker线程。 - 添加`debugger`语句到Worker脚本,断点调试。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值