每周分享:今天说说Web Worker

众所周知,JavaScript 是单线程的,任何时候只能执行一个任务,然而当今的电脑早已是多核了,似乎前端开发者并没能享受到多核的优势。然而 Web Worker 的出现解决了这个问题,真正实现了 JavaScript 的多线程执行环境。

当然,并非所有的操作都需要开一个 Web Worker,这样会浪费资源,主要是针对那些耗时的操作,会影响到主线程 UI 渲染的操作,比如一个复杂的运算。今天我们就拿文件上传来作为场景体验一下 Web Worker 的使用方式。

首先我们新建一个 index.html 文件,输入下面的代码

<html>
  <body>
    <script>
      var myWorker = new Worker('worker.js')
      myWorker.onmessage = e => {
        console.log('上传成功', e.data)
      }
      myWorker.postMessage({
        filename: 'xxx.png'
      })
    </script>
  </body>
</html>

这里我们通过构造函数 new Worker() 新建了一个 Worker 实例,传入的是一个 JS 文件的 URL,也就是我们真正的 Worker 脚本文件。

由于 Web Worker 是运行在一个单独的线程中,所以是不能直接操作主线程中的资源,自然也不能操作 documentwindow 这些DOM 对象,只能通过 onmessagepostMessage 进行两个线程之间的数据交互。

所以

myWorker.postMessage({
  base64: 'xxxxx'
})

这段代码就是用来触发上传操作,也就是告诉 Worker 要帮我上传文件了。这里传入的参数也就是要上传的文件,需要转成 base64 格式,因为 Web Worker 是不能读取本地文件的。

注意,这里是传入的是一个数据拷贝,可以类比成 Ajax 请求发送参数,对于这种对象类型的数据,postMessage 在传递之前是会将其格式化的,对方接到数据后再还原。

myWorker.onmessage = e => {
  console.log('上传成功', e.data)
}

这段代码是用来监听 Worker 的反馈,如果上传完成了,就告诉主线程。

接下来我们新建一个 worker.js 文件

var upload = (data) => {
  let _self = this
  setTimeout(function () {
    // 模拟上传....
    _self.postMessage({
      status: 'ok',
      filename: 'demo.png'
    })
  }, 3000)
}

this.onmessage = e => {
  console.log('接到上传任务')
  upload(e.data)
}

代码很简单,这里我们定义了一个 upload() 方法,并用 setTimeout() 来模拟了一个耗时的文件上传操作,通过 onmessage() 来监听主线程的消息,一旦有新的任务,就执行 upload() 上传文件,等文件上传完毕后,调用 postMessage() 方法通知主线程。

然后起一个静态文件服务器访问(注意这里不能直接访问 index.html),接着你就会看到下面的结果

接到上传任务

// 3 秒后

上传成功 {status: "ok", filename: "demo.png"}

有同学要问了,上面的 worker.js 文件是通过外部地址引入的,那能不能直接将脚本写到当前页面呢?回忆一下之前讲过的 data URI,我们完全可以利用 JavaScript 的 MIME 类型 data:text/javascript, 来组装一个 data URI。

直接上代码

<html>
  <body>
    <script type="text/js-worker" id="worker">
      var upload = (data) => {
        let _self = this
          setTimeout(function () {
            // 模拟上传....
            _self.postMessage({
              status: 'ok',
              filename: 'demo.png'
            })
          }, 3000)
        }

        this.onmessage = e => {
          console.log('接到上传任务')
          upload(e.data)
        }
    </script>
    <script>
        var script = document.querySelector('#worker').textContent
        var myWorker = new Worker('data:text/javascript,' + script)
        myWorker.onmessage = e => {
          console.log('上传成功', e.data)
        }
        myWorker.postMessage({
          base64: 'xxxxx'
        })
    </script>
  </body>
</html>

这里我们为了拿到 worker 的代码,特意将其放到了 type 为 text/js-worker<script> 标签里面,以防止其解析,当然也可以自行拼成一个字符串。

除了 data URI 还可以用 blob

var script = document.querySelector('#worker').textContent
var blob = new Blob([script], {
  type: 'text/javascript'
})
var url = window.URL.createObjectURL(blob)
var myWorker = new Worker(url)

希望大家不要将之前学过的东西忘了,当然我们也会有意地在后续文章中提及讲过的知识点来帮助大家加深印象。

其实我们可以将 Web Worker 想象成一个远程的接口,它不能干预主线程的操作,只能用来处理主线程交给它的任务,两者之间通过 postMessage 进行数据传递。

最新评论

发表评论

邮箱地址不会被公开。 必填项已用*标注