JavaScript 进阶营前端攻城狮让前端飞

Javascript:Web Worker基础

2018-02-28  本文已影响38人  Lxylona

参考资料
Web Workers
web worker详解
Blob对象

Web Worker分为专属Worker(Dedicated Worker)和共享Worker(Shared Worker)。

专属Worker与页面是一对一的关系,它们直接链接到对应的页面。

共享Worker可以由多个窗口共享,只要这些窗口和该共享worder同源。共享Worker通过处于活动中的端口和主线程们进行通信。

本质上专属Worker也是通过端口和主线程通信的,但是因为双方端口是一致的,所以把这个特性隐藏了。

本文只讨论专属Worker

一 Web Worker是什么

我们知道js是单线程的,这是浏览器的特性决定的,这意味着我们不能用js进行高复杂度的运算,因为很可能会让浏览器进入“假死”状态。
Web Worker就是来解决这个问题的。它在浏览器后台运行js代码,而不会占用网页的主线程,从而提高网页的性能。

二 和主线程的通信

既然Web Worker是一个线程,那么它跟主线程之间必定需要进行线程间通信。
Web Worker使用postMessage()方法进行通信。看下面的例子:

// index.js
var worker = new Worker('/worker.js')
worker.addEventListener('message', function (e) {
  console.log('Worker said:', e.data)
}, false)
worker.postMessage('Hello World') // 把'Hello World'发送给worker
// worker.js
self.addEventListener('message', function (e) {
    self.postMessage(e.data); // 把'Hello World'发送回给主线程
}, false)

// addEventListener也可以用onmessage来代替
// onmessage = function(e) {
//   var data = e.data;
//   ...
// };

如果不需要传递信息,只想要启动worker,可以不发送信息:

worker.postMessage() // 启动worker.

需要注意的是,通过postMessage(message)传递的message并不是两个线程共享的,而是复制一个副本
比如,现在我们传递一个对象:

// index.js
var worker = new Worker('/worker.js')
worker.addEventListener('message', function (e) {
  console.log('Worker said:', e.data.a)
  console.log('a of msg is:', msg.a)
}, false)
var msg = {
    a: 2
}
worker.postMessage(msg)
// Worker.js
self.addEventListener('message', function (e) {
    e.data.a = 3 // 修改传过来的msg中的a
    self.postMessage(e.data);
}, false)

输出是这样的:

Worker said: 3
a of msg is: 2

说明两个线程的msg并没有指向同一个对象。

三 关闭worker

如果worker已经完成了工作,那么就可以关闭这个worker,释放资源了。
关闭worker的方法有两个。

  1. 在外部执行worker.terminate()
  2. 在worker内部执行self.close()
    第二种方法比较好,因为这样可以防止意外关闭正在运行的worker

四 Worker的功能

由于Web Worker是多线程行为,所以功能是受限的,比如,它不能操作DOM。
Web Worker可以使用的功能有:

  1. navigator对象

  2. location对象(只读)

  3. XMLHttpRequest

  4. setTimeout() / clearTimeout() 和 setInterval() / clearInterval()

  5. Application Cache (AppCache)

  6. 使用 importScripts() 方法导入外部脚本
    importScripts('script1.js', 'script2.js'......);

  7. 生成其他Web Worker
    Worker 可以生成子 Worker。这对于在运行时进一步拆分大任务来说非常重要。但是,子 Worker 还有几点注意事项:

    • 子 Worker 必须托管在与父网页相同的来源中。
    • 子 Worker 中的 URI 应相对于父 Worker 的位置进行解析(与主网页不同)。

 
Web Worker不可以使用的功能:

  1. DOM
  2. window对象
  3. document对象
  4. parent对象

五 错误处理

如果在执行 Worker 时出现错误,就会触发 ErrorEvent。
三个可以帮你找出错误的实用属性:

  1. filename - 导致错误的 Worker 脚本的名称
  2. lineno - 出现错误的行号
  3. message - 有关错误的实用说明。
    可以这样处理错误:
<!-- index.html -->

<output id="error" style="color: red;"></output>
<output id="result"></output>

<script>
  function onError(e) { // 处理错误
    document.getElementById('error').textContent = [
      'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join('');
  }

  function onMsg(e) { // 处理信息
    document.getElementById('result').textContent = e.data;
  }

  var worker = new Worker('workerWithError.js');
  worker.addEventListener('message', onMsg, false);
  worker.addEventListener('error', onError, false);
  worker.postMessage(); // 启动Worker
</script>
// workerWithError.js

self.addEventListener('message', function(e) {
  postMessage(1/x); // x没有定义,抛出错误
};

六 安全限制

1. Chrome本地访问限制

由于 Google Chrome 浏览器的安全限制,Worker 无法在最新版浏览器中本地运行(例如通过 file://)。
要通过 file:// 方案运行您的应用,请使用 --allow-file-access-from-files 标记设置来运行 Chrome 浏览器。
请注意:不推荐使用此标记设置来运行您的主浏览器。此标记设置仅供测试用,请勿用于常规浏览。

其他浏览器不存在相同的限制。

2. 同源限制

只能调用同源的Worker,即host相同,协议相同

七 Worker可以做什么

计算密集型和I/O密集型的操作都很适合用Web Worker来完成:

深入了解Web Worker请戳这篇: web worker详解

八 内联Worker

有时候没有必要为一个Worker单独创建一个文件,那么可以使用Blob对象来写成内联的Worker:

// 该blob的内容为创建的worker的代码
var blob = new Blob([ "onmessage = function(e) { postMessage('msg from worker'); }"])
// 创建一个url指向该blob
var blobURL = window.URL.createObjectURL(blob)
var worker = new Worker(blobURL) // 创建一个worker,Worker接受一个url作为参数
worker.onmessage = function (e) {
    console.log(e.data)
}
worker.postMessage('inline worker!')

我们可以创建一个函数来专门把特性的函数转化为blob并返回一个url让我们使用:

function fn2workerURL (fn) {
    var blob = new Blob(['(' + fn.toString() + ')()'], {
        type: 'application/javascript'
    })
    return URL.createObjectURL(blob) // 返回一个 blob URL
}
function fnInWorker () {
    onmessage = function (e) {
        postMessage(e.data)
    }
}
var worker = new Worker(fn2workerURL(fnInWorker))
worker.onmessage = function (e) {
    console.log(e.data)
}
上一篇下一篇

猜你喜欢

热点阅读