相关免费学习推荐:javascript(视频)
这次我们会逐步讲解 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 渲染/响应的执行时间。
看一个简单的函数,计算一个数字数组的平均值:
以下是重写上述代码并“模拟”异步性的方法:
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函数,该函数将在事件循环中进一步添加计算的每个步骤。在每次计算之间,将有足够的时间进行其他计算,从而可以让浏览器进行渲染。
HTML5为我们带来了很多新的东西,包括:
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:
专用 Workers 只能被创建它的页面访问,并且只能与它通信。以下是浏览器支持的情况:
共享 Workers 在同一源(origin)下面的各种进程都可以访问它,包括:iframes、浏览器中的不同tab页(一个tab页就是一个单独的进程,所以Shared Workers可以用来实现 tab 页之间的交流)、以及其他的共享 Workers。以下是浏览器支持的情况:
Service Worker 功能:
在目前阶段,Service Worker 的主要能力集中在网络代理和离线缓存上。具体的实现上,可以理解为 Service Worker 是一个能在网页关闭时仍然运行的 Web Worker。以下是浏览器支持的情况:
本文主要讨论 专用 Workers,没有特别声明的话,Web Workers、Workers都是指代的专用 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 和创建它的页面之间进行通信,需要使用 postMessage
方法或 Broadcast Channel。
新浏览器支持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 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:
由于 JavaScript的多线程特性,Web工作者只能访问JavaScript特性的一个子集。以下是它的一些特点:
Web Workers 由于具有多线程特性,因此只能访问 JavaScript 特性的子集。 以下是可使用特性列表:
importScripts()
导入外部脚本遗憾的是,Web Workers 无法访问一些非常关键的 JavaScript 特性:
这意味着 Web Worker 不能操作 DOM (因此也不能操作 UI)。有时这可能很棘手,但是一旦你了解了如何正确使用 Web Workers,你就会开始将它们作为单独的“计算机”使用,而所有 UI 更改都将发生在你的页面代码中。 Workers 将为你完成所有繁重的工作,然后一旦完成再把结果返回给 page 页面。
和 JavaScript 代码一样,Web workers 里抛出的错误,你也需要进行处理。当 Worker 执行过程中如果遇到错误,会触发一个 ErrorEvent
事件。接口包含了三个有用的属性来帮忙排查问题:
例子如下:
在这里,可以看到我们创建了一个 worker 并开始侦听错误事件。
在 worker 内部(在 workerWithError.js
中),我们通过将未定义 x
乘以 2 来创建一个异常。异常被传播到初始脚本,然后通过页面监听 error事件,对错误进行捕获。
到目前为止,我们已经列出了 Web Workers 的优点和局限性。现在让我们看看它们最强大的用例是什么:
Spell checking(拼写检查):一个基本的拼写检查程序的工作流程如下-程序读取一个字典文件与一个正确拼写单词列表。字典被解析为一个搜索树,以使实际的文本搜索更有效。当一个单词被提供给检查器时,程序检查它是否存在于预先构建的搜索树中。如果在树中没有找到该单词,可以通过替换替换字符并测试它是否是有效的单词(如果是用户想要写的单词),为用户提供替代拼写。所有的这些处理过程都可以在 Web Worker中进行了,用户可以不被阻塞的输入词汇和句子,Web Worker 在后台校验词汇是否正确以及提供备选词汇。
想了解更多编程学习,敬请关注php培训栏目!
Atas ialah kandungan terperinci JavaScript Web Workers的构建块及5个使用场景. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!