JavaScript是如何工做的:Web Workers的构建块 + 5个使用他们的场景

摘要: 理解Web Workers。javascript

Fundebug经受权转载,版权归原做者全部。html

这是专门探索 JavaScript 及其所构建的组件的系列文章的第7篇。前端

若是你错过了前面的章节,能够在这里找到它们:java

此次咱们会逐步讲解 Web Workers,先说个简单的概念,接着讨论不一样类型的 Web Workers,他们的组成部分是如何一块儿工做的,以及不一样场景下它们各自优点和限制。最后,提供5个正确使用 Web Workers 的场景。web

正如咱们前面文章讨论的那样,你应该知道 JavaScript 语言采用的是单线程模型。然而,JavaScript 也为开发人员提供了编写异步代码的机会。算法

异步编程的局限性

之前的文章讨论过异步编程,以及应该在何时使用它。编程

异步编程可让UI界面是响应式(渲染速度快)的,经过"代码调度",让须要请求时间的代码先放到在 event loop中晚一点再执行,这样就容许UI先行渲染展现。小程序

异步编程的一个很好的用例就 AJAX 请求。因为请求可能花费大量时间,所以可使用异步请求,在客户端等待响应的同时还能够执行其余代码。segmentfault

然而,这带来了一个问题——请求是由浏览器的WEB API处理的,可是如何使其余代码是异步的呢?例如,若是成功回调中的代码很是占用CPU:微信小程序

var result = performCPUIntensiveCalculation();
复制代码

若是 performCPUIntensiveCalculation 不是一个HTTP请求而是一个阻塞代码(好比一个内容不少的for loop循环),就没有办法及时清空事件循环,浏览器的 UI 渲染就会被阻塞,页面没法及时响应给用户。

这意味着异步函数只能解决一小部分 JavaScript 语言单线程中的局限性问题。

在某些状况下,可使用 setTimeout 对长时间运行的计算阻塞的,可使用 setTimeout暂时放入异步队列中,从让页面获得更快的渲染。例如,经过在单独的 setTimeout 调用中批处理复杂的计算,能够将它们放在事件循环中单独的“位置”上,这样能够争取为 UI 渲染/响应的执行时间。

看一个简单的函数,计算一个数字数组的平均值:

如下是重写上述代码并“模拟”异步性的方法:

function averageAsync(numbers, callback) {
    var len = numbers.length,
        sum = 0;

    if (len === 0) {
        return 0;
    } 

    function calculateSumAsync(i) {
        if (i < len) {
            // Put the next function call on the event loop.
            setTimeout(function() {
                sum += numbers[i];
                calculateSumAsync(i + 1);
            }, 0);
        } else {
            // The end of the array is reached so we're invoking the callback.
            callback(sum / len);
        }
    }

    calculateSumAsync(0);
}
复制代码

使用setTimeout函数,该函数将在事件循环中进一步添加计算的每一个步骤。在每次计算之间,将有足够的时间进行其余计算,从而可让浏览器进行渲染。

Web Worker 能够解决这个问题

HTML5为咱们带来了不少新的东西,包括:

  • SSE(咱们在前一篇文章中已经描述并与WebSockets进行了比较)
  • Geolocation
  • Application cache
  • Local Storage
  • Drag and Drop
  • Web Workers

Web Worker 概述

Web Worker 的做用,就是为 JavaScript 创造多线程环境,容许主线程建立 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,二者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(一般负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

你可能会问:“JavaScript不是一个单线程的语言吗?”

事实上 JavaScript 是一种不定义线程模型的语言。Web Workers 不是 JavaScript 的一部分,而是能够经过 JavaScript 访问的浏览器特性。历史上,大多数浏览器都是单线程的(固然,这已经改变了),大多数 JavaScript 实现都入发生在浏览器中。Web Workers 不是在 Node.JS 中实现的。Node.js 中有相似的集群(cluster)、子进程概念(child_process),他们也是多线程可是和 Web Workers 仍是有区别 。

值得注意的是,规范 中提到了三种类型的 Web Workers:

Dedicated Workers

专用 Workers 只能被建立它的页面访问,而且只能与它通讯。如下是浏览器支持的状况:

Shared Workers

共享 Workers 在同一源(origin)下面的各类进程均可以访问它,包括:iframes、浏览器中的不一样tab页(一个tab页就是一个单独的进程,因此Shared Workers能够用来实现 tab 页之间的交流)、以及其余的共享 Workers。如下是浏览器支持的状况:

Service workers

Service Worker 功能:

  • 后台消息传递
  • 网络代理,转发请求,伪造响应
  • 离线缓存
  • 消息推送

在目前阶段,Service Worker 的主要能力集中在网络代理和离线缓存上。具体的实现上,能够理解为 Service Worker 是一个能在网页关闭时仍然运行的 Web Worker。如下是浏览器支持的状况:

本文主要讨论 专用 Workers,没有特别声明的话,Web Workers、Workers都是指代的专用 Workers。

Web Workers 是如何工做

Web Workers 通常经过脚本为 .js 文件来构建,在页面中还经过了一些异步的 HTTP 请求,这些请求是彻底被隐藏了的,你只须要调用 Web Worker API.

Worker 利用类线程间消息传递来实现并行性。它们保证界面的实时性、高性能和响应性呈现给用户。

Web Workers 在浏览器中的一个独立线程中运行。所以,它们执行的代码须要包含在一个单独的文件中。这一点很重要,请记住!

让咱们看看基本 Workers 是如何建立的:

var worker = new Worker('task.js');
复制代码

Worker() 构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。因为 Worker 不能读取本地文件,因此这个脚本必须来自网络。若是下载没有成功(好比404错误),Worker 就会默默地失败。

为了启动建立的 Worker,须要调用 postMessage 方法:

worker.postMessage();
复制代码

Web Worker 通讯

为了在 Web Worker 和建立它的页面之间进行通讯,须要使用 postMessage 方法或 Broadcast Channel

postMessage 方法

新浏览器支持JSON对象做为方法的第一个参数,而旧浏览器只支持字符串。

来看一个示例,经过将 JSON 对象做为一个更“复杂”的示例传递,建立 Worker 的页面如何与之通讯。传递字符串跟传递对象的方式也是同样的。

让咱们来看看下面的 HTML 页面(或者更准确地说是它的一部分):

<button onclick="startComputation()">Start computation</button>

<script> function startComputation() { worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]}); } var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) { console.log(e.data); }, false); </script>
复制代码

而后这是 worker 中的 js 代码:

self.addEventListener('message', function(e) {
  var data = e.data;
  switch (data.cmd) {
    case 'average':
      var result = calculateAverage(data); // 从数值数组中计算平均值的函数
      self.postMessage(result);
      break;
    default:
      self.postMessage('Unknown command');
  }
}, false);
复制代码

当单击该按钮时,将从主页调用 postMessage。postMessage 行将 JSON 对象传给Worker。Worker 经过定义的消息处理程序监听并处理该消息。

当消息到达时,实际的计算在worker中执行,而不会阻塞事件循环。Worker 检查传递的事件参数 e,像执行 JavaScript 函数同样,处理完成后,把结果传回给主页。

在 Worker 做用域中,this 和 self 都指向 Worker 的全局做用域。

有两种方法能够中止 Worker:从主页调用 worker.terminate() 或在 worker 内部调用 self.close()

Broadcast Channel

Broadcast Channel API 容许同一原始域和用户代理下的全部窗口,iFrames 等进行交互。也就是说,若是用户打开了同一个网站的的两个标签窗口,若是网站内容发生了变化,那么两个窗口会同时获得更新通知。

仍是不明白?就拿 Facebook 做为例子吧,假如你如今已经打开 了Facebook 的一个窗口,可是你此时尚未登陆,此时你又打开另一个窗口进行登陆,那么你就能够通知其余窗口/标签页去告诉它们一个用户已经登陆了并请求它们进行相应的页面更新。

// Connection to a broadcast channel
var bc = new BroadcastChannel('test_channel');

// Example of sending of a simple message
bc.postMessage('This is a test message.');

// Example of a simple event handler that only
// logs the message to the console
bc.onmessage = function (e) { 
  console.log(e.data); 
}

// Disconnect the channel
bc.close()
复制代码

能够从下面这张图,在视觉上来清晰地感觉 Broadcast Channel:

Broadcast Channel 浏览器支持比较有限:

消息的大小

有两种方式发送消息给Web Workers:

  • 复制消息:消息被序列化、复制、发送,而后在另外一端反序列化。页面和 Worker 不共享相同的实例,所以最终的结果是每次传递都会建立一个副本大多数浏览器,在两边都是使用的JSON对值进行编码和解码,这样对数据的解码、编码操做,势必会增长消息传输过程的时间开销。信息越大,发送的时间就越长。
  • 传递消息:这意味着原始发送方在一旦发送后不能再使用它。传输数据几乎是瞬间的,这种传输方式的局限性在于只能用 ArrayBuffer 类型来传递。

Web Workers 可用的特性

因为 JavaScript的多线程特性,Web工做者只能访问JavaScript特性的一个子集。如下是它的一些特色:

Web Workers 因为具备多线程特性,所以只能访问 JavaScript 特性的子集。 如下是可以使用特性列表:

  • navigator 对象
  • location 对象(只读)
  • MLHttpRequest
  • setTimeout()/clearTimeout() and setInterval()/clearInterval()
  • 应用缓存(Application Cache)
  • 使用 importScripts() 导入外部脚本
  • 建立其余的 Web Workers

Web Workers 的局限性

遗憾的是,Web Workers 没法访问一些很是关键的 JavaScript 特性:

  • DOM(它会形成线程不安全)
  • window 对象
  • document 对象
  • parent 对象

这意味着 Web Worker 不能操做 DOM (所以也不能操做 UI)。有时这可能很棘手,可是一旦你了解了如何正确使用 Web Workers,你就会开始将它们做为单独的“计算机”使用,而全部 UI 更改都将发生在你的页面代码中。 Workers 将为你完成全部繁重的工做,而后一旦完成再把结果返回给 page 页面。

处理错误

和 JavaScript 代码同样,Web workers 里抛出的错误,你也须要进行处理。当 Worker 执行过程当中若是遇到错误,会触发一个 ErrorEvent 事件。接口包含了三个有用的属性来帮忙排查问题:

  • filename - 致使 Worker 的脚本名称
  • lineno - 发生错误的行号
  • message - 对错误的描述

例子以下:

在这里,能够看到咱们建立了一个 worker 并开始侦听错误事件。

在 worker 内部(在 workerWithError.js 中),咱们经过将未定义 x 乘以 2 来建立一个异常。异常被传播到初始脚本,而后经过页面监听 error事件,对错误进行捕获。

5个好的 Web Workers 应用实例

到目前为止,咱们已经列出了 Web Workers 的优势和局限性。如今让咱们看看它们最强大的用例是什么:

  • Ray tracing(光线追踪):光线追踪是一种以像素为单位跟踪光的路径生成图像的渲染技术。光线追踪利用 CPU 密集型的数学计算来模拟光的路径。其思想是模拟一些效果,如反射、折射、材料等。全部这些计算逻辑均可以添加到 Web Worker 中,以免阻塞 UI线程。更好的是——能够很容易地在多个 workers 之间(以及在多个cpu之间)分割图像呈现。下面是一个使用 Web Workers 的光线追踪的简单演示—nerget.com/rayjs-mt/r.…
  • **Encryption(加密):**因为对我的和敏感数据的监管愈来愈严格,端到端加密愈来愈受欢迎。加密是一件很是耗时的事情,特别是若是有不少数据须要频繁加密(例如,在发送到服务器以前)。这是一个使用 Web Worker 很是好的场景,由于它不须要访问 DOM 或任何花哨的东西——它是完成其工做的纯算法。只要是在 Web Worker 中工做的,对于端用户就是无缝的,不会影响到体验。
  • **Prefetching data(预取数据):**为了优化你的网站或 web 应用程序并改进数据加载时间,你能够利用 Web Workers 提早加载和存储一些数据,以便在须要时稍后使用。Web Workers 在这种状况下很是棒,由于它们不会影响应用程序的UI,这与不使用Workers 时是不一样的。
  • **Progressive Web Apps(渐进式Web应用程序):**这种渐进式Web应用程序要求,即便在用户网络不稳定的条件下,也可以迅速的加载。这意味着数据必须本地存储在浏览器中。这也是 IndexDB 或相似 api 发挥做用的地方。一般状况下,客户端的存储都是必要的,但使用起来须要不阻塞UI渲染线程,那么工做就须要在 Worker 中进行了。不过,以IndexDB 为例,它提供了一些异步的API,调用它们的话也不须要使用 web worker,但若是是同步的 API,就必需要在 Worker 中使用了。
  • **Spell checking(拼写检查):**一个基本的拼写检查程序的工做流程以下-程序读取一个字典文件与一个正确拼写单词列表。字典被解析为一个搜索树,以使实际的文本搜索更有效。当一个单词被提供给检查器时,程序检查它是否存在于预先构建的搜索树中。若是在树中没有找到该单词,能够经过替换替换字符并测试它是不是有效的单词(若是是用户想要写的单词),为用户提供替代拼写。全部的这些处理过程均可以在 Web Worker中进行了,用户能够不被阻塞的输入词汇和句子,Web Worker 在后台校验词汇是否正确以及提供备选词汇。

原文:

blog.sessionstack.com...

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具Fundebug

你的点赞是我持续分享好东西的动力,欢迎点赞!

一个笨笨的码农,个人世界只能终身学习!

更多内容请关注公众号《大迁世界》!

关于Fundebug

Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎你们免费试用!

相关文章
相关标签/搜索