原文 | https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a
这是专门探索 JavaScript 及其所构建的组件的系列文章的第7篇。
这次我们会逐步讲解
Web Workers
,先说
个简单的概念,接着讨论不同类型的
Web Workers
,他们的组成部分是如何一起工作的,以及不同场景下它们各自优势和限制。
最后,提供5个正确使用
Web Workers
的场景。正如我们前面文章讨论的那样,你应该知道 JavaScript 语言采用的是单线程模型。然而,JavaScript 也为开发人员提供了编写异步代码的机会。
异步编程的局限性异步编程可以让UI界面是响应式(渲染速度快)的,通过"代码调度",让需要请求时间的代码先放到在
event loop 中晚一点再执行,这样就允许UI先行渲染展示。异步编程的一个很好的用例就
AJAX 请求。由于请求可能花费大量时间,因此可以使用异步请求,在客户端等待响应的同时还可以执行其他代码。
然而,这带来了一个问题——请求是由浏览器的WEB API处理的,但是如何使其他代码是异步的呢?例如,如果成功回调中的代码非常占用CPU:
var result = performCPUIntensiveCalculation();
如果
performCPUIntensiveCalculation 不是一个HTTP请求而是一个阻塞代码(比如一个内容很多的for loop循环),就没有办法及时清空事件循环,浏览器的
UI 渲染就会被阻塞,页面无法及时响应给用户。这意味着异步函数只能解决一小部分 JavaScript 语言单线程中的局限性问题。在某些情况下,可以使用 `setTimeout` 对长时间运行的计算阻塞的,可以使用 `setTimeout`暂时放入异步队列中,从让页面得到更快的渲染。例如,通过在单独的 `setTimeout` 调用中批处理复杂的计算,可以将它们放在事件循环中单独的“位置”上,这样可以争取为 UI
渲染/响应的执行时间。看一个简单的函数,计算一个数字数组的平均值:
以下是重写上述代码并“模拟”异步性的方法:
使用setTimeout函数,该函数将在事件循环中进一步添加计算的每个步骤。在每次计算之间,将有足够的时间进行其他计算,从而可以让浏览器进行渲染。
Web Worker 可以解决这个问题HTML5为我们带来了很多新的东西,包括:
- SSE(我们在前一篇文章中已经描述并与WebSockets进行了比较)
- Geolocation
- Application cache
- Local Storage
- Drag and Drop
- Web Workers
- 专用 Workers (Dedicated Workers)
- 共享 Workers (Shared Workers)
- 服务 Workers (Service 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 页面(或者更准确地说是它的一部分):
然后这是 worker 中的 js 代码:
当单击该按钮时,将从主页调用
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 的一个窗口,但是你此时还没有登录,此时你又打开另外一个窗口进行登录,那么你就可以通知其他窗口/标签页去告诉它们一个用户已经登录了并请求它们进行相应的页面更新。
可以从下面这张图,在视觉上来清晰地感受 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
- DOM(它会造成线程不安全)
- window 对象
- document 对象
- parent 对象