Worker对象的原理与使用:

231 阅读2分钟

1、对于web worker的认知:

web worker是浏览器独立的线程(浏览器内置的API),可以不阻塞UI线程渲染执行JS并通过message通信传递数据;

(1)不能访问window等全局属性,不能访问浏览器的内置API,也不能修改DOM;但是可以发送xhr以及fetch请求;

(2)如果需要求改UI,则可以通过message向UI线程发送消息;

同一个web worker实例还可以在不同的页面或者浏览器窗口中进行共享,这种worker称之为共享worker;

共享worker使用port对象来传输message,而独享web worker是直接使用worker实例;共享worker需要通过端口号来进行管理,而端口的管理不需要我们自己操作,使用port对象即可;发送消息与接收消息都需要使用实例worker的port属性;

2、什么时候用web worker?

例如执行一些很耗时的操作时可以用到,比如vscode的词法解析就是应用在web worker上,或者需要在前端解析大的表格但不想影响用户操作的;比如视频、图像处理、游戏、在线文档等所有需要大量计算有可能阻塞UI渲染的代码都可以放到web worker中;

3、 web worker的使用:

vue中会由于worker文件路径与打包解析等问题需要worker-loader进行编译解析,但是遇到worker is not a constructor问题,踩坑许久无果后,为了避免浪费更多的时间,最后决定使用vue-worker,造好的轮子且vue2与vue3在使用上还是有些细小的差别,下面是踩坑过程:

3.1 在vue2中使用,首先安装好worker-loader,并且在vue.config.js中配置:

configureWebpack: config => {
    config.module.rules.push({
      test: /\.worker.js$/,
      use: {
        loader: 'worker-loader',
        options: { inline: true, name: 'workerName.[hash].js' }
      }
    })
    if (isProd) {
      return {
        // 避免源码泄露
        devtool: 'nosources-source-map'
      }
    }
  },
  
 chainWebpack: (config) => {
    // config.module
    // .rule('worker')
    // .test(/\.worker\.js$/)
    // .use('worker-loader')
    // .loader('worker-loader')
    // .options({
    //   inline: 'fallback'
    // })

    // //解决worker热更新问题
    config.module.rule('.js').exclude.add(/\.worker\.js$/)

    // 解决worker线程中不存在window对象导致报错'window is undefined'问题
    config.output.globalObject('this')

    if (isProd) {
      // 打包的css如有必要加上@charset
      config.plugin('optimize-css').tap(args => {
        try {
          args[0].cssnanoOptions.preset[1].normalizeCharset = true
          return args
        } catch (_) {
          return args
        }
      })
    }
  },

(1)  创建一个worker.js文件,可以进行消息的监听收取:

onmessage = (e) => {
  console.log(e.data, '子进程')
  console.log(window, 'window')
  if (Array.isArray(e.data)) {
    e.data.push('pc')
    const data = e.data
    console.log(data, 'data 子线程')
    postMessage(data)
  }
}

(2)  引入js文件,然后new Worker类

<template>
  <div class="worker">
    <span>this is Worker single file components</span>
    <button @click="send">发 送</button>
  </div>
</template>

<script>
import Vue, { getCurrentInstance } from 'vue'
import Worker from '@/utils/worker'

export default {
  setup() {
    const worker = new Worker()
    const send = () => {
      worker.postMessage(['apple', 'banana'])
    }
    return {
      send
    }
  }
}
</script>

​ 报错:

vue.esm.js?a026:5105 [Vue warn]: Error in setup: "TypeError: _utils_worker__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor"

found in

---> <Worker> at src/pages/Worker.vue
       <HPage> at node_modules/@hui-pro/page/src/page.vue
         <App> at src/App.vue
           <Root>
warn$2 @ vue.esm.js?a026:5105
Show 1 more frame
vue.esm.js?a026:3767 TypeError: _utils_worker__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor
    at setup (Worker.vue?5aab:14)
    at invokeWithErrorHandling (vue.esm.js?a026:3735)
    at initSetup (vue.esm.js?a026:2296)
    at initState (vue.esm.js?a026:4318)
    at VueComponent.Vue._init (vue.esm.js?a026:4711)
    at new VueComponent (vue.esm.js?a026:5836)
    at createComponentInstanceForVnode (vue.esm.js?a026:5044)
    at init (vue.esm.js?a026:4906)
    at merged (vue.esm.js?a026:5061)
    at createComponent (vue.esm.js?a026:6578)

3.2 vue2中使用vue-worker:

使用vue-worker没有那么多花里胡哨,直接挂载使用即可,使用vue-worker创建一个实例,然后再调用run\post等方法发送消息即可:

<script>
import Vue, { getCurrentInstance } from 'vue'

export default {
  setup() {
    const { proxy } = getCurrentInstance()
    let work = null
    const actions = [
      {
        message: 'work-task-one',
        func: () => {
          return ['fruit', 'app']
        }
      },
      {
        message: 'work-task-two',
        func: () => {
          return ['smile', 'cry']
        }
      },
      {
        message: 'work-task-three',
        func: () => {
          return ['head', 'body']
        }
      }
    ]
    work = proxy.$worker.create(actions)
    console.log(work, 'work')
    const send = () => {
      // const arr = ['fruit', 'app']
      // console.log(arr, 'arr')
      // // run()方法:执行一次就断开,支持promise  用来执行耗时长等计算操作
      // // 参数1:函数;参数2:数组,为函数的实参
      // this.$worker.run((arg1, arg2) => {
      //   console.log(arg1, arg2, '怎么发送?')
      // }, arr)
      work.postAll().then(([res1, res2, res3]) => {
        console.log(res1, res2, res3)
      })
    }
    // work.onmessage = (e) => {
    //   console.log(e.data, 'data')
    // }
    return {
      send
    }
  }
}
</script>

3.3 vite创建的vue3模板:

使用vite创建的vue3模板可以直接引入js并正常使用,但是编辑器会报一个ts错误,目前还没有解决:

(1)  创建一个worker.js:

onmessage = (e) => {
  console.log(e.data, '子进程')
  e.data.push('pc')
  const data = e.data
  console.log(data, 'data 子线程')
  postMessage(data)
}

(2)  在模板中直接使用:

<template>
  <span>this is Worker single file components</span>
  <button @click="send">发 送</button>
  <button @click="get">接 收</button>
</template>

<script setup lang="ts">
// ts报错
import Worker from '@/utils/worker.js?worker'

const worker = new Worker()
const send = () => {
  const arr = ['fruit', 'app']
  worker.postMessage(arr)
}
worker.onmessage = (e) => {
  console.log(e.data, 'data')
}
</script>

可以正常发送接收消息:

image-20230227232747008.png