创建WebWorker的四种方式

1. 加载外部脚本文件

😏优点:简单易懂
👿缺点:不易维护

main.js
//	新建 Worker 线程(废话注释~)
var worker = new Worker("worker.js");

//	指定监听函数,接收worker线程发回来的消息。
worker.onmessage = function(e) {
  console.log(e.data); 
};

//	主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。
worker.postMessage("Test");
worker.js
//	self代表子线程自身,即子线程的全局对象
self.onmessage = function(e) {
  postMessage(e.data + "!!"); // 向主线程发送消息
};

2. 将自己封装为Worker

将主线程和worker代码写在一个文件中来将自己当作是worker,可以共享函数和类的实现。但无法共享值和实例,因为主线程和worker线程具有不同的上下文。

😏优点:共享功能的实现
👿缺点:不能用作子模块
参考:ouroboros-worker

main.js
const OuroborosWorker = require("ouroboros-worker");

/**
 * 主线程
 * @param worker
 */
function mainThread(worker) {
  window.addEventListener("DOMContentLoaded", function() {
    document.getElementById("toggle").onclick = function() {
      worker.postMessage({ type: "toggle" });
    };
    document.getElementById("reset").onclick = function() {
      worker.postMessage({ type: "reset" });
    };
    worker.onmessage = function(e) {
      var data = e.data;

      if (data.type === "counter") {
        document.getElementById("counter").textContent = data.value;
      }
    };
  });
}

/**
 * worker线程任务
 * @param self
 */
function workerThread(self) {
  var counter = 0;
  var timerId = 0;

  function toggle() {
    if (timerId === 0) {
      timerId = setInterval(function() {
        self.postMessage({ type: "counter", value: counter++ });
      }, 250);
    } else {
      clearInterval(timerId);
      timerId = 0;
    }
  }

  function reset() {
    counter = 0;
  }

  self.onmessage = function(e) {
    switch (e.data.type) {
    case "toggle":
      return toggle();
    case "reset":
      return reset();
    }
  };
}

OuroborosWorker.run(mainThread, workerThread);

3. 通过字符串创建Worker

将worker线程代码定义为变量,通过URL.createObjectURL(Blob blob)加载。
乍一看,它看起来很不错,但实际上它上限为30行,并且对编辑器不友好~!但它适合只有一点点worker代码的情况。

😏优点:可以子模块化开发
👿缺点:不方便编辑、不容易阅读、JSLint会报错
参考:How to create a Web Worker from a string

main.js
/**
 * 通过字符串创建Worker
 * @param workerCode
 * @returns {Worker}
 */
const createWorker = (workerCode) => {
    // URL.createObjectURL
    window.URL = window.URL || window.webkitURL;

    let blob;
    try {
        blob = new Blob([workerCode], {type: 'application/javascript'});
    } catch (e) { // Backwards-compatibility
        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
        blob = new BlobBuilder();
        blob.append(workerCode);
        blob = blob.getBlob();
    }
    let objectURL = URL.createObjectURL(blob);

    return new Worker(objectURL);
};

/**
 * Worker 线程所要执行的任务
 * @type {string}
 */
const code = `self.onmessage = function (e) {
    console.log('Worker Receive: '+e.data);
    self.postMessage('Worker: ' + e.data);
}`;

const worker = createWorker(code);

// Test, used in all examples:
worker.onmessage = function (e) {
    console.log('Response: ' + e.data);
};
worker.postMessage('Test');

4. 通过函数创建内联Worker

因为这种方法非常酷,我将其称为内联Worker。

利用Function.prototype.toString()获取函数源代码的字符串,再使用正则提取函数体,实现创建Worker。与第三种方式不同的是,该方法易读、易编辑,但是上下文难理解。

😏优点:可以子模块化开发、易读、易编辑
👿缺点:上下文难理解
参考:inline-worker

main.js
/**
 * 通过函数创建Worker
 * @param workerFun
 * @returns {Worker}
 */
const createWorkerFunction = (workerFun) => {
    const functionBody = workerFun.toString().trim().match(
        /^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/
    )[1];
    const blob = new Blob([functionBody], {type: "text/javascript"});
    const objectURL = URL.createObjectURL(blob);

    return new Worker(objectURL);
};

/**
 * Worker 线程所要执行的任务
 */
const workerFun = function () {
    self.onmessage = function (e) {
        console.log('Worker Receive: ' + e.data);
        self.postMessage('Worker: ' + e.data);
    };
};

const worker = createWorkerFunction(workerFun);

// Test, used in all examples:
worker.onmessage = function (e) {
    console.log('Response: ' + e.data);
};
worker.postMessage('Test');