webWorker的概念 用法 场景

这里引用上个文章的图片 这里学习下 webWorker

javascript 复制代码
<template>
  <div>
    <div style="display: flex; justify-content: center; align-items: center; margin-top: 20px;">
      <div class="image-container">
        <img src="https://i.imgur.com/cCWBPHi.png" class="final-image">
      </div>
      <div class="image-container">
        <!-- 使用 v-show 确保 canvas 元素始终存在 -->
        <canvas v-show="!isComplete" ref="canvasRef" class="canvas" />
        <img v-show="isComplete" :src="finalImageData" alt="加载完成的图片" class="final-image">
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'

// 定义变量
const canvasRef = ref(null) // canvas 元素引用
const loading = ref(false) // 加载状态
const error = ref(false) // 错误状态
const isComplete = ref(false) // 是否加载完成
const finalImageData = ref('') // 最终图片的 Base64 数据

// 创建 Worker 实例
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })

// 监听 Worker 的消息
worker.onmessage = function (event) {
  const { type, data } = event.data

  if (type === 'progress') {
    console.log(`当前分片高度: ${data.chunkHeight}px, 当前绘制位置: y = ${data.y}px`)
  }
  else if (type === 'complete') {
    console.log('图片加载完成!')
    finalImageData.value = data.imageData
    isComplete.value = true
    loading.value = false
    worker.terminate() // 释放 Worker 主线程资源
  }
  else if (type === 'error') {
    console.error('加载图片失败:', data.message)
    error.value = true
    loading.value = false
  }
}

// 点击按钮加载图片
function loadImage() {
  // 确保 canvas 元素已经渲染
  if (!canvasRef.value) {
    console.error('canvas 元素未渲染')
    return
  }

  // 将 canvas 的控制权转移到 OffscreenCanvas
  // OffscreenCanvas 是 HTML5 提供的一个 API,它允许你将 canvas 的渲染操作放到主线程之外的上下文中执行(例如 Web Worker),从而避免阻塞主线程,提升性能
  const offscreenCanvas = canvasRef.value.transferControlToOffscreen()

  // 发送任务给 Worker
  worker.postMessage(
    {
      canvas: offscreenCanvas,
      url: 'https://i.imgur.com/cCWBPHi.png',
      chunkSize: 100,
    },
    [offscreenCanvas],
  )

  loading.value = true
  error.value = false
}

// 在 onMounted 钩子中调用 loadImage,确保 DOM 已渲染
onMounted(() => {
  loadImage()
})
</script>

<style lang="scss" scoped>
.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 20px;
}

.canvas {
  border: 1px solid #ccc;
  max-width: 100%;
  max-height: 80vh;
  /* 限制 canvas 的最大高度 */
}

.final-image {
  max-width: 100%;
  max-height: 80vh;
  /* 限制图片的最大高度 */
  border: 1px solid #ccc;
}
</style>

下面是 worker.js 文件

其实通信原理和 iframe 一样 只不过可以开启多线程

javascript 复制代码
// 监听主线程的消息  这里的self也就是主线程的window对象
self.onmessage = async function (event) {
  const { canvas, url, chunkSize } = event.data

  try {
    // 加载图片
    const response = await fetch(url)
    const blob = await response.blob()
    const img = await createImageBitmap(blob)

    // 设置 canvas 尺寸
    const width = img.width
    const height = img.height
    canvas.width = width
    canvas.height = height

    const ctx = canvas.getContext('2d')

    // 分片加载逻辑
    let y = 0 // 初始化 y 坐标,表示当前绘制的位置
    const loadNextChunk = function () {
      // 计算当前分片的高度
      const chunkHeight = Math.min(chunkSize, height - y)

      // 绘制当前分片
      ctx.drawImage(img, 0, y, width, chunkHeight, 0, y, width, chunkHeight)
      y += chunkHeight // 更新 y 坐标,准备绘制下一个分片

      // 发送进度信息给主线程
      self.postMessage({
        type: 'progress',
        data: { chunkHeight, y },
      })

      // 如果未加载完,继续加载下一个分片
      if (y < height) {
        requestAnimationFrame(loadNextChunk) // 使用 requestAnimationFrame 优化渲染
      }
      else {
        // 加载完成,将 canvas 转换为图片并发送给主线程
        canvas.convertToBlob().then((blob) => {
          const reader = new FileReader()
          reader.readAsDataURL(blob)
          reader.onloadend = function () {
            self.postMessage({
              type: 'complete',
              data: { imageData: reader.result },
            })
          }
        })
      }
    }

    // 开始加载第一个分片
    loadNextChunk()
  }
  catch (e) {
    console.error('加载图片失败:', e)
    self.postMessage({
      type: 'error',
      data: { message: e.message },
    })
  }
}

今天,我在学习Web Workers的时候,遇到了一些概念,感觉有点复杂。我决定仔细研究一下,看看能不能理解清楚。

首先,我知道Web Workers是一种在后台线程中运行JavaScript代码的方法,可以用来处理复杂的计算任务,从而不阻塞主线程,提升用户体验。但具体的实现方式,我还不太清楚。

我看到代码中使用了new Worker()方法来创建一个Web Worker。这让我想到,是不是在主线程中创建了一个新的线程,专门用来处理特定的任务?好像是这样。然后,主线程通过worker.postMessage()方法向Worker发送任务。这让我联想到Vue.js中父组件和子组件之间的通信,父组件通过$emit方法向子组件传递数据,而子组件通过事件监听来接收数据。难道这里也是类似的机制?

于是,我查找了一些资料,发现确实,postMessage方法在主线程和Worker之间传递数据,就像组件之间的事件传递一样。主线程发送消息,Worker接收并处理,然后通过self.postMessage()将结果发送回去。这确实有点像Vue2中的父子传值,只不过这里的"子"是Worker线程。

接下来,我看到在主线程中使用了worker.onmessage来监听Worker的消息。这部分有点让我困惑。我想,onmessage是不是一种事件监听器,当Worker发送消息回来时,主线程就能接收到并处理?这和Vue中的事件处理很像,父组件监听子组件的事件,当子组件触发事件时,父组件执行相应的处理函数。

为了更好地理解,我尝试写了一个简单的例子。主线程创建一个Worker,然后通过postMessage发送一个计算任务,Worker接收到任务后进行计算,然后通过postMessage把结果送回主线程。主线程监听到消息后,处理结果并显示出来。运行后,发现确实有效,计算任务在后台线程完成,主线程继续处理其他事情。

但是,我也有疑问。比如,Worker和主线程之间的通信是不是只能通过postMessage?有没有其他方式?另外,self在Worker中的作用是什么?为什么不能用this或者其他变量?

我查阅了相关文档,了解到在Worker中,self是指向Worker全局对象的,可以用它来发送和接收消息。而主线程和Worker之间的通信只能通过postMessage方法,这是为了确保线程安全,避免共享内存带来的问题。

还有一个问题,如果主线程和Worker之间传递的是对象,会发生什么?会不会导致数据共享的问题?文档中提到,传递的是结构化克隆,而不是引用,因此不会共享内存,每个线程都有自己的数据副本,这样就避免了竞态条件和数据不一致的问题。

通过这个学习过程,我对Web Workers有了更深入的理解,特别是它们和Vue组件之间的类比,帮助我更好地记忆和应用这些概念。虽然有些细节还需要进一步探索,但总体上,我觉得自己已经掌握了一个基本的框架。

总结:

使用 new Worker() 方法在主线程创建了一个 Web Worker,主线程通过 worker.postMessage() 发送任务给 Worker,并在主线程开启 worker.onmessage 监听 Worker 的消息。这与 Vue2 中的父子组件通信机制相似,主线程类似于父组件,Worker 线程类似于子组件。Worker.js 文件中通过 self.postMessage() 将结果发送回主线程,主线程在接收到消息后进行处理。

相关推荐
Sweet_vinegar3 天前
vim临时文件泄露
linux·安全·网络安全·vim·web·ctf·ctfshow
飞3003 天前
4399 web后端开发岗内推
大数据·python·php·web
葡萄_成熟时_3 天前
JavaWeb后端基础(3)
java·开发语言·web
仙魁XAN3 天前
Flutter 学习之旅 之 flutter 使用 webview_flutter 进行网页加载显示
flutter·web·webview·网页显示·webview_flutter
2401_882726484 天前
组态软件在物联网中的应用
java·物联网·struts·低代码·web
葡萄_成熟时_4 天前
JavaWeb后端基础(2)
java·网络·web
葡萄_成熟时_5 天前
JavaWeb后端基础(1)
java·maven·web
轨迹H5 天前
BUUCTF-Web方向21-25wp
前端·网络安全·web·ctf·buuctf
i建模5 天前
Git最佳实践指南(Windows/Linux双系统详解)
linux·windows·git·web