HTML5之多线程(Web Worker)

HTML教程 2025-07-30

提到 HTML5 总是让人津津乐道,太多的特性和有趣的 API 让人耳目一新。但是很多童鞋还停留在语义化的阶段,忽视了 HTML5 的强劲之处。

这节我们来探讨一下多线程 Web-Worker。

一、明确 JavaScript 是单线程

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。

听起来有些匪夷所思,为什么不设计成多线程提高效率呢?我们可以假设一种场景:

假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM

这决定了它只能是单线程,否则会带来很复杂的同步问题。为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,估计短期内很难改变。

二、新曙光:Web Worker

单线程始终是一个痛点,为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。但是子线程完全受主线程控制,且不得操作DOM

所以,这个新标准并没有改变JavaScript单线程的本质。

Web Workers是现代浏览器提供的一个JavaScript多线程解决方案,我们可以找到很多使用场景:

1.我们可以用Web Worker做一些大计算量的操作;

2.可以实现轮询,改变某些状态;

3.页头消息状态更新,比如页头的消息个数通知;

4.高频用户交互,拼写检查,譬如:根据用户的输入习惯、历史记录以及缓存等信息来协助用户完成输入的纠错、校正功能等

5.加密:加密有时候会非常地耗时,特别是如果当你需要经常加密很多数据的时候(比如,发往服务器前加密数据)。

6.预取数据:为了优化网站或者网络应用及提升数据加载时间,你可以使用Workers

来提前加载部分数据以备不时之需。

加密是一个使用Web Worker的绝佳场景,因为它并不需要访问DOM或者利用其它魔法,它只是纯粹使用算法进行计算而已。随着大众对个人敏感数据的日益重视,信息安全和加密也成为重中之重。这可以从近期的 12306 用户数据泄露事件中体现出来。

一旦在 Worker 进行计算,它对于用户来说是无缝地且不会影响到用户体验。

三、兼容性

四、基本概念

1.首先记得去判断是否支持

if (window.Worker) {  ...}

2.创建一个新的worker很简单

const myWorker = new Worker('worker.js');

postMessage() 方法和 onmessage 事件处理函数是 Workers 的黑魔法。

3.postMessage用来发送消息,而onmessage用来监听消息

const worker = new Worker('src/worker.js');worker.onmessage = e => {  console.log(e.data);};worker.postMessage('你好吗!');

在主线程中使用时,onmessagepostMessage()必须挂在worker对象上,而在worker中使用时不用这样做。原因是,在worker内部,worker是有效的全局作用域。

4.异常处理:

worker.onerror = function(error) {  console.log(error.message);  throw error;};

5.终止worker

worker.terminate();

worker线程会被立即杀死,不会有任何机会让它完成自己的操作或清理工作。

6.在worker线程中,workers也可以调用自己的close方法进行关闭:

close();

五、快速开始

为了快速掌握,我们来做一个小例子:项目结构如下

├── index.html└── src    ├── main.js    └── worker.js

Html

  Web Work Demo    
Hello Jartto!

main.js

const worker = new Worker('src/worker.js');worker.onmessage = e => {  const message = e.data;  console.log(`[From Worker]: ${message}`);  document.getElementById('app').innerHTML = message;};worker.postMessage('写的真好!');

Work.js

onmessage = e => {  const message = e.data;  console.log(`[From Main]: ${message}`);  if(message.indexOf('好') > -1) {    postMessage('谢谢支持');  }};

代码很简单,主线程发送:「写的真好!」

web worker 收到消息,发现内容中含有「好」字,回传给主线程:「谢谢支持」

六、局限性

1.在worker内,不能直接操作DOM节点,也不能使用window对象的默认方法和属性。然而我们可以使用大量window对象之下的东西,包括WebSocketsIndexedDB以及FireFox OS专用的Data Store API等数据存储机制。

这里举个例子,我们修改main.js

const worker = new Worker('src/worker.js');worker.onmessage = e => {  const message = e.data;  console.log(`[From Worker]: ${message}`);  document.getElementById('app').innerHTML = message;};+ worker.onerror = function(error) {+   console.log(error);+   worker.terminate();+ };worker.postMessage('写的真好!');

再来修改work.js

+ alert('jartto');onmessage = e => {  const message = e.data;  console.log(`[From Main]: ${message}`);  if(message.indexOf('好') > -1) {    postMessage('谢谢支持');  }};

这时候运行就会报出:

这是因为:worker.js执行的上下文,与主页面HTML执行时的上下文并不相同,最顶层的对象并不是Windowwoker.js执行的全局上下文,而是WorkerGlobalScope,我们具体说明。

2.workers和主线程间的数据传递通过这样的消息机制进行:双方都使用postMessage()方法发送各自的消息,使用onmessage事件处理函数来响应消息(消息被包含在Message事件的data属性中)。

这个过程中数据并不是被共享而是被复制。

3.同源限制

分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源。

4.文件限制

Worker线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自服务器。

5.不允许本地文件

Uncaught SecurityError: Failed to create a worker:
script at '(path)/worker.js'
cannot be accessed from origin 'null'.

Chrome doesn’t let you load web workers when running scripts from a local file.

那如何解决呢?我们可以启动一个本地服务器,建议使用http-server,简单易用。

6.内容安全策略

有别于创建它的document对象,worker有它自己的执行上下文。因此普遍来说,worker并不受限于创建它的document(或者父级worker)的内容安全策略。

我们来举个例子,假设一个document有如下头部声明:

Content-Security-Policy: script-src 'self'

这个声明有一部分作用在于,禁止它内部包含的脚本代码使用eval()方法。然而,如果脚本代码创建了一个worker,在worker上下文中执行的代码却是可以使用eval()的。

为了给 worker 指定 CSP,必须为发送 worker 代码的请求本身加上一个 CSP。

有一个例外情况,即worker脚本的源如果是一个全局性的唯一的标识符(例如,它的URL指定了数据模式或者blob),worker则会继承创建它的document或者workerCSP

七、扩展:WorkerGlobalScope

关于 ,我们可以在MDN上面找到文档:

1.self

我们可以使用WorkerGlobalScopeself属性来获取这个对象本身的引用。

2.location

location属性返回当线程被创建出来的时候与之关联的WorkerLocation对象,它表示用于初始化这个工作线程的脚步资源的绝对URL,即使页面被多次重定向后,这个URL资源位置也不会改变。

3.close

关闭当前线程,与terminate作用类似。

4.caches

当前上下文得CacheStorage,确保离线可用,同时可以自定义请求的响应。

5.console

支持console语法。

6.importScripts
我们可以通过importScripts()方法通过urlworker中加载库函数。

7.XMLHttpRequest
有了它,才能发出Ajax请求。

8.可以使用:

  • setTimeout/setInterval
  • addEventListener/postMessage

还有很多API可以使用,这里就不一一举例了。

八、异常处理

worker出现运行中错误时,它的onerror事件处理函数会被调用。它会收到一个扩展了ErrorEvent接口的名为error的事件。该事件不会冒泡并且可以被取消。

为了防止触发默认动作,worker 可以调用错误事件的 preventDefault() 方法。

错误事件我们常用如下这三个关键信息:

  • Message:可读性良好的错误消息;
  • Filename:发生错误的脚本文件名;
  • Lineno:发生错误时所在脚本文件的行号;
worker.onerror = function(error) {  console.log(error.message);  throw error;};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

注:相关教程知识阅读请移步到HTML教程频道。