web worker介绍以及应用场景

web worker介绍以及应用场景

  • 什么是web worker ?
  • 为什么要用web worker ?
  • 怎么使用web worker ?
    • 1.主线程采用new 命令,调用worker构造函数,Worker中接收两个参数,一个url,就是worker执行计算的脚本的路径url(为必须参数),worker线程所要执行的任务,这个脚本必须来自网络,放到服务器上,如果脚本没下载成功,就会失败,第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程;
    • 2.javascript主线程中和worker子线程中通过postMessage和onmessage来通信,由主线程中postMessage(params),传入参数,子线程通过onmessage函数接收从主线程传递过来的参数,通过获取参数中的data来取值。
    • 3.worker中加载其他的脚本,有一个专门的方法importScripts()。
    • 4.监听错误
  • 实例代码
  • web worker 注意事项:
  • web worker的应用场景有哪些?

面试遇到一个问题,就是如果有几十万条数据,前端该怎么处理?
我回答用分页,懒加载去加载数据,但是面试官说后台不给处理,前端该怎么处理?
如果都渲染到页面上来,浏览器根本就承担不了,这个问题真的没回答上来,
查了一下就查到了web worker,新的知识点,当然要知道what,why,how了。

什么是web worker ?

web worker 是Web应用程序在独立于主线程的后台线程中,运行一个脚本的操作。使用构造函数(例如,Worker())创建一个 worker 对象, 构造函数接受一个 JavaScript文件URL , 这个文件包含了将在 worker 线程中运行的代码脚本。

主线程和 worker 线程相互之间使用 postMessage() 方法来发送信息, 并且通过 onmessage这个 event handler来接收信息(传递的信息包含在 Message 这个事件的data属性内) 。数据的交互方式为传递副本,而不是直接共享数据。

worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope
在这里插入图片描述

为什么要用web worker ?

在浏览器中javascript的执行是单线程的,一个线程一次只能做一件事情,这样必然会出现阻塞的情况,非常影响用户体验,所以ajax应运而生了。ajax的出现使得页面在等待服务器响应的这段时间内不再发生阻塞,但是这仍然没有改变代码单线程执行的本质,这也意味着我们依旧不能把耗费时间的复杂运算放在页面上执行。而web worker的出现弥补了这个缺点。
web worker为 JavaScript 创造多线程环境,允许主线程通过new运算符创建 Worker 线程(子线程),将一些任务分配给Worker线程,这样一些计算量大的、耗费时间的、复杂的运算都可以放到worker线程上,避免主线程被阻塞。

怎么使用web worker ?

1.主线程采用new 命令,调用worker构造函数,Worker中接收两个参数,一个url,就是worker执行计算的脚本的路径url(为必须参数),worker线程所要执行的任务,这个脚本必须来自网络,放到服务器上,如果脚本没下载成功,就会失败,第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程;

2.javascript主线程中和worker子线程中通过postMessage和onmessage来通信,由主线程中postMessage(params),传入参数,子线程通过onmessage函数接收从主线程传递过来的参数,通过获取参数中的data来取值。

myworker.postMessage()方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。

//主线程
let  myworker = new Worker('worker.js');
 this.myWorker.postMessage([value1, value2])

子线程接收到主线程传递过来的条件参数,将经过处理的数据通过postMessage(Data)传递给主线程,

//子线程
onmessage = function(e) {
    console.log(e.data, 'Worker: Message received from main script')
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        const workerResult = 'Result: ' + result
        console.log('Worker: Posting message back to main script')
        postMessage(workerResult)
    }
}

接着,主线程通过myworker.onmessage指定监听函数,这样主线程就接收到子线程发回来的消息了

//主线程
 this.myWorker.onmessage = function (e) {
      result.textContent = e.data
      console.log('Message received from worker')
 }

主线程接收到经过子线程处理过的数据之后可以关闭子线程:

this.myworker.terminate()

worker线程中有一个监听函数,监听message事件,以下写法二和写法三中self和this都可以,worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope,self和this都指向这个对象,(标准 workers 由单个脚本使用; 共享workers使用SharedWorkerGlobalScope)
写法一:

onmessage = function(e) {
 postMessage('You said: ' + e.data);
}

写法二:

self.addEventListener('message', function(e) {
    
    postMessage('You said: ' + e.data);
})

写法三:

this.addEventListener('message', function(e) {
   postMessage('You said: ' + e.data);
})

写法四:

addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);

根据主线程发来的数据,Worker 线程可以调用不同的方法,下面是一个例子。

self.addEventListener('message', function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);

上面代码中,self.close()用于在 Worker 内部关闭自身。

3.worker中加载其他的脚本,有一个专门的方法importScripts()。

importScripts('script1.js');

可同时加载多个脚本

importScripts('script1.js', 'script2.js');

4.监听错误

主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。

worker.onerror(function (event) {
  console.log([
    'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
  ].join(''));
});

// 或者
worker.addEventListener('error', function (event) {
  // ...
});

Worker 内部也可以监听error事件。

2.5 关闭 Worker
使用完毕,为了节省系统资源,必须关闭 Worker。

// 主线程
worker.terminate();
// Worker 线程
self.close();

实例代码

以下例子在vue项目中使用如下:

webWorker.vue

<template>
    <div  tabindex="0">
      <h1>Web Workers</h1>
      <form>
        <div>
          <label for="number1">number 1: </label>
          <input type="text" id="number1" :value="inputValue1" @change="handleChange1">
        </div>
        <div>
          <label for="number2">number 2: </label>
          <input type="text" id="number2" :value="inputValue2" @change="handleChange2">
        </div>
      </form>
      <p class="result">Result: 0</p>
      </div>
</template>
<script>
export default {
  data () {
    return {
      inputValue1: 0,
      inputValue2: 0
    }
  },
  methods: {
    workerFun (value1, value2, type) {
      const result = document.querySelector('.result')
      if (window.Worker) {
        this.myWorker = new Worker('worker.jsx')
        this.myWorker.postMessage([value1, value2])
        console.log('Message posted to worker', type)
        this.myWorker.onmessage = function (e) {
          result.textContent = e.data
             this.myworker.terminate()
          console.log('Message received from worker')
        }
      } else {
        console.log('Your browser doesn\'t support web workers.')
      }
    },
    handleChange1 (e) {
      this.inputValue1 = e.target.value
      this.workerFun(e.target.value, this.inputValue2, 'first')
    },
    handleChange2 (e) {
      this.inputValue2 = e.target.value
      this.workerFun(this.inputValue1, e.target.value, 'second')
    }
  }
}
</script>

worker.jsx放到根目录下面,代码如下:

onmessage = function(e) {
    console.log(e.data, 'Worker: Message received from main script')
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        const workerResult = 'Result: ' + result
        console.log('Worker: Posting message back to main script')
        postMessage(workerResult)
    }
}

web worker 注意事项:

(1)同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。

(3)通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制

Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

(5)文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

web worker的应用场景有哪些?

一般情况下,很少会用到web Worker,返回到浏览器到数据在后台已经经过计算处理,不太可能让前端处理复杂到业务逻辑
webWorker解决的是js中数据处理导致的UI线程阻塞,所以webWorker的应用场景一般为需要大量计算的时候,这将避免ui界面渲染被阻塞
大数据的处理:

这里所说的大数据处理,并不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。

高频的用户交互:

高频的用户交互适用于根据用户的输入习惯、历史记录以及缓存等信息来协助用户完成输入的纠错、校正功能等类似场景,用户频繁输入的响应处理同样可以考虑放在web worker中执行。

最后声明一点,了解后台的同学千万不要认为web worker等同于后台意义的多线程,web worker现在有了多线程的形(有了多线程的用法),但还不具备多线程的神(不具备线程通信、锁等后台线程的基本特性),web worker的本质是支持我们把数据刷新与页面渲染两个动作拆开执行(不使用web worker的话这两个动作在主线程中是线性执行的)。

为了防止主线程造成拥堵,可以开启worker线程,这样不影响主线程的数据加载,可以在worker中进行数据请求,也可以创建多个worker线程,不过Web worker也有一些使用限制:无法访问DOM节点,无法访问全局变量或是全局函数。所以之前用的是jQuery Ajax发送请求的,这里就不能用了;只能用原生的ajax请求啦。

self.addEventListener('message', function(e) {
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        var xhr = new XMLHttpRequest()
        let ajaxResult
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    ajaxResult = JSON.parse(xhr.responseText)
                    const workerResult = 'Result: ' + result
                    postMessage({
                        workerResult,
                        ajaxResult: ajaxResult.result
                    })
                }
            }
        }

        xhr.open('get', 'https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json', true)
        xhr.send(null)
    }
})

如有错误请指出,谢谢。
参考:
http://www.ruanyifeng.com/blog/2018/07/web-worker.html
https://blog.csdn.net/lqlqlq007/article/details/79824122

你可能感兴趣的:(javascript,js,web,worker,javascript)