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 语言的单线程本质。