Skip to content

web worker 多线程

一个 worker 是使用一个构造函数创建的一个对象,参数可以是远程 JavaScript 脚本(必须遵守同源策略),这也可以是一个 Blob 数据; worker 运行在另一个全局上下文中,不同于当前的 window,因此在 Worker 内通过 window 获取全局作用域将返回错误。

一个专用 worker 仅仅能被首次生成它的脚本使用,一个共享 worker 可以被多个脚本使用,即使这些脚本正在被不同的 window、iframe 或者 worker 访问。

页面与 worker 不会共享同一个实例,最终的结果就是在每次通信结束时生成了数据的一个副本。

worker 中可用的函数和接口

  • Navigator
  • XMLHttpRequest
  • Array, Date, Math, and String
  • setTimeout and setInterval

在一个 worker 中最主要不能做的事情就是直接影响父页面。包括操作父页面的节点以及使用页面中的对象。

专用 worker

js
// 主线程中
const worker = new Worker('worker.js')
worker.postMessage('主线程发送的消息')
worker.onmessage = function (e) {
  console.log(e)
}

// worker.js
onmessage = function (e) {
  postMessage('worker发送的消息')
}

有时 worker 处理需要根据内容定义,上面这种方式显然不合适,此时可以使用 Blob URL 作为参数。

js
const msg = '自定义消息'
const blo = new Blob(
  [
    `onmessage = function (data) {
      console.log(data);
      this.postMessage('${msg}');
    };`
  ],
  { type: 'text/javascript' }
)
const url = URL.createObjectURL(blo)
const worker = new Worker(url)

worker.postMessage('主线程发送的消息')
worker.onmessage = function (data) {
  console.log(data)
}

终止 worker

js
// 主线线程
worker.terminate()

// worker.js
close()

处理错误

js
worker.onerror = function (e) {
  e.message // 错误信息
  e.filename // 脚本名
  e.lineno // 错误行号
}

引入脚本与库

importScripts 函数接受 0 个或者多个 URI 作为参数来引入资源。浏览器加载并运行每一个列出的脚本。每个脚本中的全局对象都能够被 worker 使用。

脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到所有脚本都下载并运行完毕,importScripts() 才会返回。

js
importScripts()
importScripts('a.js')
importScripts('a.js', 'b.js')

共享 worker

共享 worker 可以被多个浏览上下文调用,所有这些浏览上下文必须属于同源(相同的协议,主机和端口号)。

在传递消息之前,端口连接必须被显式的打开,打开方式是使用 onmessage 事件处理函数或者 start 方法。start 方法的调用只在一种情况下需要,那就是消息事件被 addEventListener() 方法注册。父级线程和 worker 线程需要双向通信,那么它们都需要调用 start() 方法。

当一个端口连接被创建时,worker 内使用 onconnect 事件处理函数来执。

js
// 主线程
const sWorker = new SharedWorker('worker.js')
sWorker.port.onmessage = function () {} // 不需要 start
sWorker.port.addEventListener('message', function () {}) // 需要
sWorker.port.start()

// worker.js
onconnect = function (e) {
  const port = e.ports[0]
  port.onmessage = function () {} // 不需要 start
  port.addEventListener('message', function () {}) // 需要
  port.start()
}

使用场景

  1. 计算量庞大
  2. worker 中可以通过 importScript(url)来加载其他文件
  3. 可以使用 XMLHttpRequest 来发送请求

局限性

  1. 同源限制,分配给 worker 线程的文件,必须与主线程同源
  2. DOM 限制,子线程中无法访问主线程所在网页的 dom 对象,无法使用 document、window、parent 等对象
  3. 通信限制,主线程和子线程必须通过 postMessage 进行通信
  4. 脚本限制,worker 线程中不允许使用 alert()和 confirm()
  5. 文件限制,worker 线程无法读取本地文件,只能是来自网络的文件

严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了 javascript 语言的单线程本质。