前言

这里不要被标题误导,我打引号了,JavaScript并不能实现多线程(至少到目前为止,未来应该也不会)。

虽然JavaScript是单线程的,但是浏览器是多线程的啊,我们可以借助浏览器再跑一个JavaScript线程来帮助主JavaScript线程减轻压力,JavaScript之所以是单线程的原因是要保持数据的一致性,防止多个线程修改数据导致数据不一致,所以虽然可以使用worker,但也要遵循一些规范

  • 同源限制:分配给 Web Worker 线程运行的脚本文件,必须与主线程的脚本文件同源
  • DOM 限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用 document、window、parent 这些对象。但是,Worker 线程可以访问 navigator 对象和 location 对象。
  • 通信联系:Worker 线程和主线程不再同一个上下文环境,它们不能直接通信,必须通过消息完成。
  • 脚本限制:Worker 线程不能执行 alert()confirm() 方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求
  • 文件限制:Worker 线程无法读取本地文件,即不能打开本地的文件系统(file://) ,它所加载的脚本,必须来自网络

创建worker

在使用worker时我们需要使用构造方法Worker实例化一个worker实例,传入worker文件的地址或者使用字符串转URL,例如

// 方法一
const worker = new Worker('./worker.js')

// 方法二
const code = `
    // worker's code
`
const buffer = new Blob([code])
const url = URL.createObjectURL(buffer)
const worker = new Worker(url)

创建worker对象后:

  • 使用postMessage方法可以向worker发送消息,消息可以是任何类型,包括二进制;
  • 使用onmessage方法用来监听worker的消息;
  • 使用onerror方法用来监听worker线程的报错;
  • 使用terminate方法用来结束worker线程。
function startWorker() {
  const worker = new Worker('./worker.js')

  worker.postMessage({ x: 1, y: 2 })
  worker.onmessage = ({ data }) => {
    alert(data.data);
  }
  worker.onerror = (e) => {
    console.log(`[ERROR: Line ${e.lineno}] in ${e.filename}: ${e.message}`);
  }
}

worker内,通过self来访问worker自身全局,

  • 使用onmessage方法处理主进程中通过postMessage方法触发的事件;
  • 使用postMessage方法向主线程发送数据,同样可以是任何数据类型;
  • worker线程中也可以引入其他js文件,使用importScripts(path)来引入;
  • worker可以使用close方法来主动关闭线程。
self.onmessage = ({ data }) => {
  // 一般使用worker来处理耗时任务,所以使用定时器模拟
  setTimeout(() => {
    self.postMessage({ data: data.x + data.y })
    throw new Error('抛出异常')
    self.close()
  }, 5000)
}

以上类似onmessage的形式,均可以通过addEventListener来代替

实战练习

任务说明

在HTML页面中通过按钮触发worker事件:运算、退出……

main.js

const worker = new Worker('./worker.js')

worker.addEventListener('message', ({ data }) => {
  console.log(data);
})

worker.addEventListener('error', (e) => {
  console.log(`[ERROR: Line ${e.lineno}] in ${e.filename}: ${e.message}`);
})

function cal() {
  const num1 = document.getElementById('num1').value
  const num2 = document.getElementById('num2').value

  worker.postMessage({ cmd: 'cal', data: { num1, num2 } })
}

function exit() {
  worker.postMessage({ cmd: 'exit' })
  // 或者worker.terminate()
}

Worker.js

self.addEventListener('message', ({ data }) => {
  switch (data.cmd) {
    case 'cal':
      const result = Number(data.data.num1) + Number(data.data.num2)
      if (isNaN(result)) {
        throw new Error('Input Type Error')
      } else {
        self.postMessage(result)
      }
      break
    case 'exit':
      self.close()
      break
    default:
      self.postMessage('Unknown command')
  }
})

index.html

<body>
  num1: <input type="text" id="num1" />
  num2: <input type="text" id="num2" />
  <button type="button" onclick="cal()">计算</button>
  <button type="button" onclick="exit()">关闭</button>
</body>

效果演示:

0q9us-8nwkv11

应用场景

WebWorker 带来后台计算能力,WebWorker 自身是由 Webkit 多线程实现,但它并没有为 JavaScript 语言带来多线程编程特性,我们现在仍然不能在 JavaScript 代码中创建并管理一个线程,或者主动控制线程间的同步与锁等特性。Web Worker 只是浏览器(宿主环境)提供的一个能力 / API。而且它不支持 IE。

使用专用线程进行数学运算Web Worker 最简单的应用就是用来做后台计算,而这种计算并不会中断前台用户的操作

图像处理通过使用从 Canvas 或者 Video 元素中获取的数据,可以把图像分割成几个不同的区域并且把它们推送给并行的不同 Workers 来做计算

大量数据的检索当需要在调用 AJAX 后处理大量的数据,如果处理这些数据所需的时间长短非常重要,可以在 Web Worker 中来做这些,避免冻结 UI 线程。

背景数据分析由于在使用 Web Worker 的时候,我们有更多潜在的 CPU 可用时间,我们现在可以考虑一下 JavaScript 中的新应用场景。我们现在可以考虑一下 JavaScript 中的新应用场景。例如,我们可以想像在不影响 UI 体验的情况下实时处理用户输入。利用这样一种可能,我们可以想像一个像 Word(Office Web Apps 套装)一样的应用:当用户打字时后台在词典中进行查找,帮助用户自动纠错等等。


前端小白