Web Worker 初探

之前咱们总说,JS是单线程没有多线程,当JS在页面中运行长耗时同步任务的时候就会致使页面假死影响用户体验,从而须要设置把任务放在任务队列中;执行任务队列中的任务也并不是多线程进行的,然而如今HTML5提供了咱们前端开发这样的能力 - Web Workers API,咱们一块儿来看一看 Web Worker 是什么,怎么去使用它,在实际生产中如何去用它来进行产出。javascript

1. 概述

Web Workers 使得一个Web应用程序能够在与主执行线程分离的后台线程中运行一个脚本操做。这样作的好处是能够在一个单独的线程中执行费时的处理任务,从而容许主(一般是UI)线程运行而不被阻塞。html

它的做用就是给JS创造多线程运行环境,容许主线程建立worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。这样作的好处是主线程能够把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言自己支持了多线程能力,而是浏览器做为宿主环境提供了JS一个多线程运行的环境。前端

不过由于worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,可是也会形成资源的浪费,因此不该过分使用,用完注意关闭。或者说:若是worker无实例引用,该worker空闲后当即会被关闭;若是worker实列引用不为0,该worker空闲也不会被关闭。java

看一看它的兼容性web

Browser IE Edge FireFox Chrome Safari
version 10+ 12+ 3.5+ 4+ 4+

2. 使用

2.1 限制

worker线程的使用有一些注意点ajax

  1. 同源限制
    worker线程执行的脚本文件必须和主线程的脚本文件同源,这是固然的了,总不能容许worker线程到别人电脑上处处读文件吧
  2. 文件限制
    为了安全,worker线程没法读取本地文件,它所加载的脚本必须来自网络,且须要与主线程的脚本同源
  3. DOM操做限制
    worker线程在与主线程的window不一样的另外一个全局上下文中运行,其中没法读取主线程所在网页的DOM对象,也不能获取 documentwindow等对象,可是能够获取navigatorlocation(只读)XMLHttpRequestsetTimeout族等浏览器API。
  4. 通讯限制
    worker线程与主线程不在同一个上下文,不能直接通讯,须要经过postMessage方法来通讯。
  5. 脚本限制
    worker线程不能执行alertconfirm,但可使用 XMLHttpRequest 对象发出ajax请求。

2.2 例子

在主线程中生成 Worker 线程很容易:算法

var myWorker = new Worker(jsUrl, options)

Worker()构造函数,第一个参数是脚本的网址(必须遵照同源政策),该参数是必需的,且只能加载 JS 脚本,不然报错。第二个参数是配置对象,该对象可选。它的一个做用就是指定 Worker 的名称,用来区分多个 Worker 线程。canvas

// 主线程

var myWorker = new Worker('worker.js', { name : 'myWorker' });

// Worker 线程
self.name // myWorker

关于api什么的,直接上例子大概就能明白了,首先是worker线程的js文件:segmentfault

// workerThread1.js

let i = 1

function simpleCount() {
  i++
  self.postMessage(i)
  setTimeout(simpleCount, 1000)
}

simpleCount()

self.onmessage = ev => {
  postMessage(ev.data + ' 呵呵~')
}

在HTML文件中的body中:api

<!--主线程,HTML文件的body标签中-->

<div>
  Worker 输出内容:<span id='app'></span>
  <input type='text' title='' id='msg'>
  <button onclick='sendMessage()'>发送</button>
  <button onclick='stopWorker()'>stop!</button>
</div>

<script type='text/javascript'>
  if (typeof(Worker) === 'undefined')    // 使用Worker前检查一下浏览器是否支持
    document.writeln(' Sorry! No Web Worker support.. ')
  else {
    window.w = new Worker('workerThread1.js')
    window.w.onmessage = ev => {
      document.getElementById('app').innerHTML = ev.data
    }
    
    window.w.onerror = err => {
      w.terminate()
      console.log(error.filename, error.lineno, error.message) // 发生错误的文件名、行号、错误内容
    }
    
    function sendMessage() {
      const msg = document.getElementById('msg')
      window.w.postMessage(msg.value)
    }
    
    function stopWorker() {
      window.w.terminate()
    }
  }
</script>

能够本身运行一下看看效果,上面用到了一些经常使用的api

主线程中的api,worker表示是 Worker 的实例:

  • worker.postMessage: 主线程往worker线程发消息,消息能够是任意类型数据,包括二进制数据
  • worker.terminate: 主线程关闭worker线程
  • worker.onmessage: 指定worker线程发消息时的回调,也能够经过worker.addEventListener('message',cb)的方式
  • worker.onerror: 指定worker线程发生错误时的回调,也能够 worker.addEventListener('error',cb)

Worker线程中全局对象为 self,表明子线程自身,这时 this指向self,其上有一些api:

  • self.postMessage: worker线程往主线程发消息,消息能够是任意类型数据,包括二进制数据
  • self.close: worker线程关闭本身
  • self.onmessage: 指定主线程发worker线程消息时的回调,也能够self.addEventListener('message',cb)
  • self.onerror: 指定worker线程发生错误时的回调,也能够 self.addEventListener('error',cb)

注意,w.postMessage(aMessage, transferList) 方法接受两个参数,aMessage 是能够传递任何类型数据的,包括对象,这种通讯是拷贝关系,便是传值而不是传址,Worker 对通讯内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通讯内容串行化,而后把串行化后的字符串发给 Worker,后者再将它还原。一个可选的 Transferable对象的数组,用于传递全部权。若是一个对象的全部权被转移,在发送它的上下文中将变为不可用(停止),而且只有在它被发送到的worker中可用。可转移对象是如ArrayBuffer,MessagePort或ImageBitmap的实例对象,transferList数组中不可传入null。

更详细的API参见 MDN - WorkerGlobalScope

worker线程中加载脚本的api:

importScripts('script1.js')    // 加载单个脚本
importScripts('script1.js', 'script2.js')    // 加载多个脚本

3. 实战场景

我的以为,Web Worker咱们能够当作计算器来用,须要用的时候掏出来摁一摁,不用的时候必定要收起来~

  1. 加密数据
    有些加解密的算法比较复杂,或者在加解密不少数据的时候,这会很是耗费计算资源,致使UI线程无响应,所以这是使用Web Worker的好时机,使用Worker线程可让用户更加无缝的操做UI。
  2. 预取数据
    有时候为了提高数据加载速度,能够提早使用Worker线程获取数据,由于Worker线程是能够是用 XMLHttpRequest 的。
  3. 预渲染
    在某些渲染场景下,好比渲染复杂的canvas的时候须要计算的效果好比反射、折射、光影、材料等,这些计算的逻辑可使用Worker线程来执行,也可使用多个Worker线程,这里有个射线追踪的示例
  4. 复杂数据处理场景
    某些检索、排序、过滤、分析会很是耗费时间,这时可使用Web Worker来进行,不占用主线程。
  5. 预加载图片
    有时候一个页面有不少图片,或者有几个很大的图片的时候,若是业务限制不考虑懒加载,也可使用Web Worker来加载图片,能够参考一下这篇文章的探索,这里简单提要一下。

    // 主线程
    let w = new Worker("js/workers.js");
    w.onmessage = function (event) {
      var img = document.createElement("img");
      img.src = window.URL.createObjectURL(event.data);
      document.querySelector('#result').appendChild(img)
    }
    
    // worker线程
    let arr = [...好多图片路径];
    for (let i = 0, len = arr.length; i < len; i++) {
        let req = new XMLHttpRequest();
        req.open('GET', arr[i], true);
        req.responseType = "blob";
        req.setRequestHeader("client_type", "DESKTOP_WEB");
        req.onreadystatechange = () => {
          if (req.readyState == 4) {
          postMessage(req.response);
        }
      }
      req.send(null);
    }

在实战的时候注意

  • 虽然使用worker线程不会占用主线程,可是启动worker会比较耗费资源
  • 主线程中使用XMLHttpRequest在请求过程当中浏览器另开了一个异步http请求线程,可是交互过程当中仍是要消耗主线程资源

在 Webpack 项目里面使用 Web Worker 请参照:怎么在 ES6+Webpack 下使用 Web Worker

至于还有Shared Worker、Service Worker什么的,咱们就不看了,IE不喜欢


网上的帖子大多深浅不一,甚至有些先后矛盾,在下的文章都是学习过程当中的总结,若是发现错误,欢迎留言指出~

参考:

  1. MDN - Web Workers 概念与用法
  2. 阮一峰 - Web Worker 使用教程
  3. JavaScript 工做原理之七-Web Workers 分类及 5 个使用场景
  4. Web Worker在项目中的妙用
  5. 怎么在 ES6+Webpack 下使用 Web Worker

PS:欢迎你们关注个人公众号【前端下午茶】,一块儿加油吧~

另外能够加入「前端下午茶交流群」微信群,长按识别下面二维码便可加我好友,备注加群,我拉你入群~

相关文章
相关标签/搜索