浅析Web Worker及实践

Posted 赏花赏景赏时光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析Web Worker及实践相关的知识,希望对你有一定的参考价值。

本文讲述内容如下所示:

  1. Web Worker概述
  2. API
  3. Web Worker在实际项目中应用
  4. 总结

一、Web Worker概述

1、Web Worker产生背景

        众所周知javascript是单线程的语言,所有任务只能在一个线程上完成,一次只能做一件事,即前面的任务还没有完成,后面的任务只能排队等待。如果前面的任务需要执行一些大数据量的计算,页面就会出现卡顿、点击无反应、甚至页面崩溃等现象。这对用户体验而言是非常糟糕的。

        在这种情况下,Web Worker可以为js提供一个多线程环境 ,主线程可以将一些耗时、复杂的计算任务分配给Worker线程 ,两者可以同时运行,互不干扰,等到Worker线程任务完成后,再把结果发送给主线程。这样Worker线程负责耗时、复杂的计算任务,主线程主要负责界面渲染不会被阻塞,使得用户体验更流畅。

2、在浏览器控制台下,直观认识Web Worker

如下图所示,打开浏览器的控制台,点击Source项,看到小齿轮的文件,就是在项目中使用Worker做一些复杂、耗时的任务

3、使用 Web Worker注意项

1)同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源;

2)DOM限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象,及其这些对象的属性和方法,比如alert、confirm等,但Worker 线程可以使用navigator对象和location对象;

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

4、Web Worker浏览器支持程度

二、API

1、主线程

在主线程里面通过new 一个Worker线程对象,用来在主线程操作Worker。Worker线程对象的属性和方法如下:

1、Worker.onerror:指定 error 事件的监听函数。
2、Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
3、Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
4、Worker.postMessage():向 Worker 线程发送消息。
5、Worker.terminate():立即终止 Worker 线程

2、worker线程

Web Worker 有自己的全局对象,不同于window对象,而是一个专门为 Worker 定制的全局对象,因此定义在window上面的对象和方法,在Web Worker 内不是全部都可以使用的。Worker 线程有一些自己的全局属性和方法:

self指向产生的这个worker线程,可不用书写self

1、self.name: Worker 的名字。该属性只读,由构造函数指定。
2、self.onmessage:指定message事件的监听函数。
3、self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
4、self.close():关闭 Worker 线程。
5、self.postMessage():向产生这个 Worker 线程发送消息。
6、self.importScripts():加载 JS 脚本

三、Web Worker在实际项目中的应用

1、使用Web Worker背景

前端调后台接口获取源数据,如下图所示,接口返回数据整体用时1.15s,数据量1.5MB,这时候前端需要对返回的1.5MB数据进行处理,如果这个处理直接放在主线程的话,会造成页面卡顿、点击无反应、甚至页面崩溃等问题。因此我们需要将这部分数据处理逻辑放在另一个线程中处理,避免干扰主线程。

 2、在webpack中配置Web Worker

插件worker-loader可以将js脚本注册为Web Worker

1)在项目中安装插件worker-loader

npm i worker-loader -D

2)在webpack的module.rules中配置以下规则


  test: /\\.worker\\.js$/, // 以.worker.js结尾的文件将被worker-loader加载
  loader: 'worker-loader',
  options: 
     inline: true, // 将 worker作为blob进行内联;内联模式将额外为浏览器创建chunk,即使对于不支持内联worker的浏览器也是这样的
     publicPath: assetsPublicPath   // 资源路径前缀
   
 

3)新建a.worker.js文件---该文件定义了worker对象线程要处理的任务,onmessage可接收主线程创建的worker对象线程发送的消息,postMessage()可向主线程的worker对象线程发送消息

onmessage = function (evt) 
    // 工作线程收到消息,传送的数据放在evt.data属性中
    var  arr, type, expand  = evt.data
    var departmentTreeIdArr
    // 根据不同的行为做相应的事件处理
    if (type === 'initData') 
        // 从接口拿到数据后,需要初始化结构
        departmentTreeIdArr = getDepts(arr, expand)
     else if (type === 'choose') 
        // 去重
        departmentTreeIdArr = removeRepeatData(arr)
    
    // 事件处理完之后,向主线程的worker对象线程发送消息,返回处理后的结果
    postMessage(
        departmentTreeIdArr,
        type
    )


function getDepts(arr, expand) 
    // toDo something
    // egs.
    return arr


function removeRepeatData(arr) 
  // to do something
  // egs.
  return arr

4)在需要引进worker的vue文件中,引进a.worker.js文件,生成worker对象,向工作线程发送消息,并监听工作线程发送的信息

a. 引进a.worker.js文件:

<script>
  import Worker from './a.worker.js' // 引进worker文件
  ...
</script>

b. 在mounted里面创建worker对象

<script>
import Worker from './a.worker.js' // 引进worker文件
export default 
  data: () => 
    return 
      worker: null // 保存worker对象
    
  ,
  mounted() 
    this.worker = new Worker() // 生成worker对象
  

</script>

c. getDepartments方法调后台接口获取源数据,并通过worker.postMessage方法将源数据传给worker进行处理

<script>
import Worker from './a.worker.js' // 引进worker文件
export default 
  data: () => 
    return 
      worker: null // 保存worker对象
    
  ,
  mounted() 
    this.worker = new Worker() // 生成worker对象
    this.getDepartments() // 调接口获取源数据
  ,
  methods: 
    getDepartments() 
      let url = '接口路径' 
      this.$rootStore.state.loading.status = true
      this.http(url).get().then((res) => 
        // res的数据达到2万多条,所以放worker处理,要不然页面会被卡死 
        this.worker.postMessage( // 通过worker的postMessage方法将数据发送给worker进行处理
          arr: res, 
          type: 'initData', 
          expand: this.expand 
        ) 
      ) 
    
  

d. 在mounted中监听worker向主线程发送处理完数据之后的消息

<script>
import Worker from './a.worker.js' // 引进worker文件
export default 
  data: () => 
    return 
      worker: null // 保存worker对象
    
  ,
  mounted() 
    this.worker = new Worker() // 生成worker对象
    this.getDepartments() // 调接口获取源数据
    
    this.worker.addEventListener('message', function (event)  // 监听message事件
      let departmentTreeIdArr, type = event.data // worker处理完后发送回来的数据在event.data中
      if (type === 'initData')  
        vm.dataTree = departmentTreeIdArr 
       else if (type === 'choose') 
        vm.$emit('on-choose-more-department', departmentTreeIdArr) 
         
      )
    
  ,
  methods: 
    getDepartments() 
      let url = '接口路径' 
      this.$rootStore.state.loading.status = true
      this.http(url).get().then((res) => 
        // res的数据达到2万多条,所以放worker处理,要不然页面会被卡死 
        this.worker.postMessage( // 将数据发送给worker进行处理
          arr: res, 
          type: 'initData', 
          expand: this.expand 
        ) 
      ) 
    
  

</script>

至此,在项目中使用Web Worker的流程就说完了

四、总结

当项目中有遇到大量数据处理的场景时候,可使用Web Worker来创建Worker子线程,在Worker子线程内进行运算,处理完后的结果再返回给主线程,避免主线程被阻塞,导致界面卡顿、崩溃等现象。

参考文章:

1、https://www.ruanyifeng.com/blog/2018/07/web-worker.html

2、https://blog.csdn.net/terrychinaz/article/details/115176725

3、https://webpack.html.cn/loaders/worker-loader.html

4、https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers

以上是关于浅析Web Worker及实践的主要内容,如果未能解决你的问题,请参考以下文章

web worker计算md5实践及遇到的坑

借助Service Worker和cacheStorage缓存及离线开发 (转载)

组件化解耦 | 浅析ARouter路由发现原理与简单实践

浅析正则表达式基本语法及应用详解

zan框架入门(三)——原理浅析

大象无形_虚幻引擎程序设计浅析pdf