这一次咱们将分拆Web Workers:咱们将提供一个概述,讨论不一样类型的workers,他们的组成部分如何共同发挥做用,以及他们在不一样状况下提供的优点和局限性。最后,咱们将提供5个用例,其中Web Workers将是不错的选择。html
您应该已经熟悉JavaScript在单个线程上运行的事实,由于咱们以前已经详细讨论过它。可是,JavaScript也为开发人员提供了编写异步代码的机会。ajax
咱们以前已经讨论过异步编程,什么时候应该使用它。算法
异步编程经过在事件循环中“调度”要稍后执行的部分代码来使应用程序UI可以响应,从而容许首先执行UI渲染。编程
异步编程的一个很好的用例是制做AJAX请求。 因为请求可能须要很长时间,所以能够异步制做请求,而且在客户端等待响应时,能够执行其余代码。api
// This is assuming that you're using jQuery jQuery.ajax({ url: 'https://api.example.com/endpoint', success: function(response) { // Code to be executed when a response arrives. } });
可是,这会带来一个问题 - 请求由浏览器的WEB API处理,但其余代码如何能够异步? 例如,若是成功回调中的代码是CPU密集型的:数组
var result = performCPUIntensiveCalculation();
若是performCPUIntensiveCalculation不是一个HTTP请求,而是一个阻塞代码(例如一个巨大的for循环),则没法释放事件循环并解除浏览器的用户界面 - 它会冻结并对用户无响应。浏览器
这意味着异步函数仅解决JavaScript语言的单线程局限性的一小部分。缓存
在某些状况下,经过使用setTimeout,您能够在从更长时间运行的计算中解除UI的状况下取得良好结果。例如,经过在独立的setTimeout调用中对复杂的计算进行批处理,您能够将它们置于事件循环中的单独“位置”,这样就能够得到UI渲染/响应性所需的时间。安全
咱们来看看一个计算数值数组平均值的简单函数:服务器
function average(numbers) { var len = numbers.length, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += numbers[i]; } return sum / len; }
这就是你如何重写上面的代码并“模拟”异步性:
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 Workers是浏览器内的线程,可用于执行JavaScript代码而不会阻止事件循环。
这真是太神奇了。 JavaScript的整个范例基于单线程环境的思想,但在这里来自网络工做者(Web Workers),它解除了(部分)这种限制。
Web Workers容许开发人员将长时间运行和计算密集型任务放在后台,而不会阻止用户界面,从而使您的应用程序更具响应能力。更重要的是,为了解决事件循环的问题,不须要使用setTimeout技巧。
下面是一个简单的演示,显示了在有和没有Web Workers的状况下对数组进行排序的区别。
Web Workers容许您执行诸如启动长时间运行的脚原本处理计算密集型任务,但不会阻止UI。实际上,这一切都是平行进行的。Web Workers真正是多线程的。
你可能会说 - “JavaScript不是单线程语言吗?”。
当你意识到JavaScript是一种没有定义线程模型的语言时,这应该是你的'aha!'时刻。 Web Workers不是JavaScript的一部分,它们是能够经过JavaScript访问的浏览器功能。大多数浏览器从来都是单线程的(固然,这已经改变了),而且大多数JavaScript实现都发生在浏览器中。 Web Worker没有在Node.JS中实现 - 它有一个“cluster”或“child_process”的概念,有点不一样。
值得注意的是,这个规范提到了三种类型的Web Workers:
专用Web Worker由主进程实例化,而且只能与其进行通讯。
共享工做人员能够经过运行在同一来源的全部进程(不一样的浏览器选项卡,iframe或其余共享工做人员)访问。
服务工做人员是一个事件驱动的工做人员,针对原点和路径进行了注册。 它能够控制与之关联的网页/网站,拦截并修改导航和资源请求,并以很是细化的方式缓存资源,从而使您能够很好地控制应用在某些状况下的行为方式(例如,当网络不是可用。)
在这篇文章中,咱们将关注Dedicated Workers(专职工做者)并将他们称为“(Web Workders)网络工做者”或“(Workers)工做者”。
Web Workers被实现为.js文件,这些文件经过页面中的异步HTTP请求提供。 Web Worker API彻底隐藏了这些请求。
工做人员利用相似线程的消息传递来实现并行性。 它们很是适合保持您的用户界面的最新性,性能和响应能力。
Web工做人员在浏览器中的独立线程中运行。 所以,它们执行的代码须要包含在单独的文件中。 记住这一点很是重要。
让咱们看看如何建立一个基本的工做人员:
var worker = new Worker('task.js');
若是“task.js”文件存在且可访问,浏览器将产生一个新的线程,以异步方式下载文件。 下载完成后,它将被执行,工做人员将开始工做。
若是提供的文件路径返回404,工做人员将自动失败。
为了启动建立的worker,你须要调用postMessage方法:
worker.postMessage();
为了在Web Worker和建立它的页面之间进行通讯,您须要使用postMessage方法或广播频道。
较新的浏览器支持将JSON对象做为该方法的第一个参数,而旧版浏览器只支持一个字符串。
让咱们看一个建立worker的页面如何与它进行通讯的例子,经过传递一个JSON对象做为一个更“复杂”的例子。 传递一个字符串是彻底同样的。
咱们来看看下面的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>
这就是咱们的工做人员脚本的外观:
self.addEventListener('message', function(e) { var data = e.data; switch (data.cmd) { case 'average': var result = calculateAverage(data); // Some function that calculates the average from the numeric array. self.postMessage(result); break; default: self.postMessage('Unknown command'); } }, false);
点击按钮后,将从主页面调用postMessage。 worker.postMessage行将JSON对象传递给worker,并添加cmd和数据键及其各自的值。 工做人员将经过定义的消息处理程序处理该消息。
当消息到达时,实际的计算正在工做者中执行,而不会阻塞事件循环。 工做人员正在检查传递的事件e并执行,就像标准的JavaScript函数同样。 完成后,结果会传回主页面。
在一名工人的背景下,自我和这一点都指向了工人的全球范围。
有两种方法能够阻止工做人员:经过从主页面调用worker.terminate()或在worker自己内部调用self.close()。
广播频道是更通用的通讯API。 它容许咱们将消息广播到共享相同来源的全部上下文。 全部浏览器选项卡,iframe或从同一来源提供服务的工做人员均可以发送和接收消息:
// 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()
在视觉上,你能够看到广播频道的样子,使其更加清晰:
虽然广播频道的浏览器支持有限,
有两种方式将消息发送给Web Workers:
因为Web Worker的多线程特性,Web Worker只能访问JavaScript特性的一个子集。如下是功能列表:
可悲的是,Web Workers没有访问一些很是关键的JavaScript特性:
这意味着Web Worker不能操纵DOM(以及UI)。它有时可能会很是棘手,可是一旦您学会如何正确使用Web Workers,您将开始将它们做为单独的“计算机”使用,而全部UI更改将发生在您的页面代码中。工做人员将为您完成全部繁重的工做,一旦工做完成,您会将结果传递给对UI进行必要更改的页面。
与任何JavaScript代码同样,您须要处理在Web Workers中引起的任何错误。若是在执行工做时发生错误,则会触发ErrorEvent。该接口包含三个有用的属性,用于肯定出错的地方:
这是一个例子:
function onError(e) { console.log('Line: ' + e.lineno); console.log('In: ' + e.filename); console.log('Message: ' + e.message); } var worker = new Worker('workerWithError.js'); worker.addEventListener('error', onError, false); worker.postMessage(); // Start worker without a message.
self.addEventListener('message', function(e) { postMessage(x * 2); // Intentional error. 'x' is not defined. };
在这里,您能够看到咱们建立了一geWorker并开始监听错误事件。
在worker的内部(在workerWithError.js中),咱们经过将x乘以2来建立一个有意的异常,而未在该范围中定义x。 异常传播到初始脚本,而且正在调用有关错误信息的onError。
到目前为止,咱们列出了Web Workers的优点和局限性。如今让咱们看看最佳应用场景: