浅析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

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值