Skip to content
On this page

web worker

TIP

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。 也就是前端得多线程。应用场景在大数据需要计算,或者多个模块需要计算、统计等。可以把每个小模块都拆分成一个web worker线程。(例如:项目本来6个模块,每个模块计算需要6s,合计就是6 * 6 = 36s了,假如我们拆分成6个web worker分别执行。那就是1s左右就能返回结果了,不会阻塞主线程,是不是时间上提高了很多, 对用户体验也提升了很多)。

worker 特性检测

TIP

为了更好的错误处理控制以及向下兼容,将你的 worker 运行代码包裹在以下代码中是一个很好的想法

js
if (window.Worker) {
  console.log('support web worker')
} else {
  console.log('no support web worker')
}

使用web worker

TIP

使用new Worker()创建一个web worker, 入参可以是数据、函数...。 个人理解就是把数据或者传到webworker里面去执行, 他里面得js代码是局部(封闭)得,就是需要用传参得方式去进行数据交互。

示例:

js
// index.html
 const worker = new Worker('./index.js');

  worker.onmessage = function (event) { // 外部接受返回得数据
    console.log(event, 'web worker success response')
  }

  worker.onerror = function (event) {
    console.log(event, '[error]: web worker error response')
  }

  worker.postMessage('主线程向web工作线程发送得消息'); // 使用postMessage 向web worker 发送消息


// index.js 也就是我web Worker传进去得index.js路劲

// 以下是index.js代码
this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
  // 工作线程收到主线程的消息
  console.log(event, 'event')

  this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
    value: '工作线程向主线程发送消息'
  });
};

TIP

上面就是一个简单得web worker 示例了(需要使用vs code得 Live Server就是对index.html起一个服务不然会提示你路径加载错误找不到index.js)。 使用new Worker()创建web 线程, 在工作线程和主线程用onmessage()方法做监听是否使用postMessage()通信。

web Worker应用

TIP

上面咱们看到了怎么使用web worker,那咱们能把web worker应用在那个场景呢? 比如:我需要随机生成5次 1000000个数字,并排序得到排序后得数据。

js
// index.html

function createdWorker() {
  const worker = new Worker('./index.js');

    worker.onmessage = function (event) { // 外部接受返回得数据
      console.log(event, 'web worker success response')
    }

    worker.onerror = function (event) {
      console.log(event, '[error]: web worker error response')
    }
    worker.postMessage('执行1'); // 使用postMessage 向web worker 发送消息
}

  createdWorker(); // 调用1次
  createdWorker(); // 调用2次
  createdWorker(); // 调用3次
  createdWorker(); // 调用4次
  createdWorker(); // 调用5次


  // index.js代码
function computedCallback() { // web worker 计算方法
    const randomVal = Math.random() * 100;
    const data = [];
    for (let i = 0; i < 1000000; i++) {
      data.push(Math.random() * 1000)
    }

    return data.sort((a, b) => a - b)
}


this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
  // 工作线程收到主线程的消息
  console.log(event, 'event')

  this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
    value: computedCallback(),
  });
};

TIP

经过上面得实验发现几乎是web worker几乎是同一时间返回得结果(原来得1/5时间),我也用不使用得web worker测试了(需要加载很久)下面是使用原来js写的都是统计100000个数字进行排序(给我卡的就想直接关浏览器)。

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>

</html>

<script>
  // index.js 监听消息
  function computedCallback() { // web worker 计算方法
    const randomVal = Math.random() * 100;
    const data = [];
    for (let i = 0; i < 1000000; i++) {
      data.push(Math.random() * 1000)
    }

    return data.sort((a, b) => a - b)
  }
  console.log(computedCallback(), 1)
  console.log(computedCallback(), 2)
  console.log(computedCallback(), 3)
  console.log(computedCallback(), 4)
  console.log(computedCallback(), 5)
</script>

总结:
可以看的出使用web worker得好处了吧,不阻塞线程并差不多在同一时间返回结果。

web worker 销毁

TIP

在我使用完需要销毁掉,我下次需要用到在重新创建就好了 不用一直建立一个web worker工作线程。用之创建,用完销毁。

  1. 在主线程销毁创建得web worker,使用terminate()方法。还以上面代码为例,可以在控制台开发者模式得Sources查看到是否进程被销毁
js
// index.html
function createdWorker() {
    const worker = new Worker('./index.js');

    worker.onmessage = function (event) { // 外部接受返回得数据
      console.log(event, 'web worker success response');
      // 在接收到我想要得结果后,就不需要这个线程了。
      worker.terminate(); // 终止、销毁
    }

    worker.onerror = function (event) {
      console.log(event, '[error]: web worker error response')
    }
    worker.postMessage('执行1'); // 使用postMessage 向web worker 发送消息
  }

  createdWorker();
  createdWorker();
  createdWorker();
  createdWorker();
  createdWorker();
  1. web worker任务线程使用close方法进行销毁、关闭。可以在控制台开发者模式得Sources查看到是否进程被销毁
js
// index.js 任务js
// index.js 监听消息
function computedCallback() { // web worker 计算方法
  const data = [];
  for (let i = 0; i < 1000000; i++) {
    data.push(Math.random() * 1000)
  }

  return data.sort((a, b) => a - b)
}

this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
  // 工作线程收到主线程的消息
  console.log(event, 'event');


  this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
    value: computedCallback(),
  });

  this.close(); // 新增
};

在项目中使用web Worker

TIP

在vue项目中是否可以使用web worker呢?当然可以得。在当时调研时发现了一个simple-web-worker 库 他就是对web worker 进行了一系列封装,让你得web worker更简单。

核心代码

js
// createDisposableWorker.js
export const createDisposableWorker = response => {
  const URL = window.URL || window.webkitURL
  const blob = new Blob([response], { type: 'application/javascript' }) // eslint-disable-line
  const objectURL = URL.createObjectURL(blob)
  const worker = new Worker(objectURL) // eslint-disable-line
  worker.post = message =>
    new Promise((resolve, reject) => {
      worker.onmessage = event => {
        URL.revokeObjectURL(objectURL)
        resolve(event.data)
      }
      worker.onerror = e => {
        console.error(`Error: Line ${e.lineno} in ${e.filename}: ${e.message}`)
        reject(e)
      }
      worker.postMessage({ message })
    })
  return worker
}



const makeResponse = work => `
  self.onmessage = function(event) {
    const args = event.data.message.args
    if (args) {
      self.postMessage((${work}).apply(null, args))
      return close()
    }
    self.postMessage((${work})())
    return close()
  }
`

解读

TIP

使用URL.createObjectURL()创建一个url, 这个url是用传进来得js。就是给index.js创建一个url提拱给 web worker去用。 makeResponse方法就是这个indexjs的模板。我又基于前面核心代码封了一层代码如下:

js
// dispatch.js
const makeResponse = (work) => `
  self.onmessage = function(event) {
    self.postMessage((${work})(event.data));
  }
`;

const createDisposableWorker = (response) => {
  const URL = window.URL || window.webkitURL;
  const blob = new Blob([response], { type: 'application/javascript' });
  const objectURL = URL.createObjectURL(blob);
  const worker = new Worker(objectURL);
  worker.post = (...args) => new Promise((resolve, reject) => {
    worker.onmessage = (event) => {
      URL.revokeObjectURL(objectURL);
      resolve(event.data);
    };
    worker.onerror = (e) => {
      console.error(`[web Worker]: Line ${e.lineno} in ${e.filename}: ${e.message}`);
      reject(e);
    };
    worker.postMessage(...args);
  });
  return worker;
};

export default class Dispatcher {
  constructor(fn) {
    this.worker = createDisposableWorker(makeResponse(fn));
  }

  dispatch(event) {
    return this.worker.post(event);
  }

  dispose() {
    this.worker.terminate();
  }
}

使用方法

js
// 引入封装好的 Dispatcher
import Dispatcher from 'dispatch.js'
// 引入 computedCallback函数
import computedCallback from 'computed.worker.js'

const worker = new Dispatcher(computedCallback); // 把方法放入Dispatcher中
worker.dispatch()
  .then((res) => {
    console.log(res); // 计算好的数据、结果

    // 使用dispose 终止web worker
    worker.dispose();
});

源码

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>

</html>

<script>
  const makeResponse = (work) => `
  self.onmessage = function(event) {
    self.postMessage((${work})(event.data));
  }
`;

  const createDisposableWorker = (response) => {
    const URL = window.URL || window.webkitURL;
    const blob = new Blob([response], { type: 'application/javascript' });
    const objectURL = URL.createObjectURL(blob);
    const worker = new Worker(objectURL);
    worker.post = (...args) => new Promise((resolve, reject) => {
      worker.onmessage = (event) => {
        URL.revokeObjectURL(objectURL);
        resolve(event.data);
      };
      worker.onerror = (e) => {
        console.error(`[web Worker]: Line ${e.lineno} in ${e.filename}: ${e.message}`);
        reject(e);
      };
      worker.postMessage(...args);
    });
    return worker;
  };

  class Dispatcher {
    constructor(fn) {
      this.worker = createDisposableWorker(makeResponse(fn));
    }

    dispatch(event) {
      return this.worker.post(event);
    }

    dispose() {
      this.worker.terminate();
    }
  }



  function computedCallback() { // web worker 计算方法
    const randomVal = Math.random() * 100;
    const data = [];
    for (let i = 0; i < 1000000; i++) {
      data.push(Math.random() * 1000)
    }

    return data.sort((a, b) => a - b)
  }


  function createdWorker() {
    const worker = new Dispatcher(computedCallback); // 创建

    worker.dispatch().then((res) => {
      console.log('web worker success response', res);

      worker.dispose(); // 销毁
    })
  }



  createdWorker();
  createdWorker();
  createdWorker();
  createdWorker();
  createdWorker();

</script>

结束语

TIP

其他方式在项目中引入web worker也都有尝试,总觉得用起来还要配置一些webpack来比较麻烦,个人认为还是用这种方案比较好用。 经过这篇文章相信对web worker有了一个深刻的认识并且能在项目中应用起来的。

Released under the MIT License.