JavaScript 語言的眾多設計目標之一是保持單線程,並且簡單。儘管我必須承認,考慮到語言結構的特性,它絕非簡單!但我們所說的「單線程」是指 JavaScript 中只有一個控制線程;是的,遺憾的是,你的 JavaScript 引擎一次只能做一件事。
現在,對於使用電腦上閒置的多核心處理器來說,這聽起來是不是太受限了? HTML5 有望改變這一切。
Web Workers 生活在一個無法存取 DOM 的受限世界中,因為 DOM 不是線程安全的。
一種學派認為 JavaScript 的單執行緒本質是一種簡化,但另一種流派則將其視為一種限制。後一組有一個非常好的觀點,特別是當現代 Web 應用程式大量使用 JavaScript 來處理 UI 事件、查詢或輪詢伺服器端 API、處理大量資料以及根據伺服器回應操作 DOM 時。 p>
能夠在單一控制執行緒中執行如此多的操作,同時保持響應式UI 通常是一項艱鉅的任務,它迫使開發人員訴諸駭客和解決方法(例如使用setTimeout()
, setInterval()
,或使用XMLHttpRequest
和DOM事件)來實現並發。然而,值得注意的是,這些技術肯定提供了一種非同步呼叫的方法,但非阻塞並不一定意味著並發。 John Resig 在他的部落格上解釋了為什麼不能並行運行任何東西。
如果您使用 JavaScript 有相當長的時間,您很可能遇到以下煩人的對話框,指出某些腳本執行時間過長。是的,幾乎每次您的頁面停止回應時,原因都可以歸因於某些 JavaScript 程式碼。
#以下是瀏覽器在執行腳本時可能會掛起的一些原因:
...非阻塞不一定意味著並發...
感謝 HTML5 和 Web Workers,您現在可以產生一個新執行緒——提供真正的非同步。新工作執行緒可以在主執行緒處理 UI 事件時在背景運行,即使工作執行緒正忙於處理大量資料。例如,工作人員可以處理大型 JSON 結構以提取有價值的資訊以在 UI 中顯示。但我的廢話已經夠多了;讓我們來看看一些實際的程式碼。
通常,與 Web Worker 相關的程式碼會駐留在單獨的 JavaScript 檔案中。父執行緒透過在 Worker
建構函式中指定腳本檔案的 URI 來建立一個新的 Worker,該 Worker 會非同步載入並執行 JavaScript 檔案。
var primeWorker = new Worker('prime.js');
要啟動一個工作線程,父線程會向該工作線程發送一條訊息,如下所示:
var current = $('#prime').attr('value'); primeWorker.postMessage(current);
父頁面可以使用 postMessage
API 與工作人員通信,該 API 也用於跨來源訊息傳遞。除了向工作程序發送原始資料類型之外,postMessage
API 還支援傳遞 JSON 結構。但是,您不能傳遞函數,因為它們可能包含對底層 DOM 的參考。
父執行緒和工作執行緒有自己獨立的空間;來回傳遞的訊息是複製的,而不是共享的。
在幕後,這些訊息在工作執行緒處序列化,然後在接收端反序列化。因此,不鼓勵向工作線程發送大量資料。
父執行緒也可以註冊一個回呼來偵聽工作執行緒在執行其任務後傳回的任何訊息。這允許父線程在工作線程發揮作用後採取必要的操作(例如更新 DOM)。看看這段程式碼:
primeWorker.addEventListener('message', function(event){ console.log('Receiving from Worker: '+event.data); $('#prime').html( event.data ); });
event
物件包含兩個重要屬性:
<b>target</b>
:用於識別發送訊息的worker;主要在多工作人員環境中有用。 <b>data</b>
:worker 傳回其父執行緒的訊息。 worker 本身包含在 prime.js
中,並註冊從其父級接收的 message
事件。它也使用相同的 postMessage
API 與父執行緒通訊。
self.addEventListener('message', function(event){ var currPrime = event.data, nextPrime; setInterval( function(){ nextPrime = getNextPrime(currPrime); postMessage(nextPrime); currPrime = nextPrime; }, 500); });
网络工作者生活在一个受限且线程安全的环境中。
在此示例中,我们只需找到下一个最大素数,然后重复将结果发送回父线程,父线程又用新值更新 UI。在工作者上下文中, self
和 this
均指全局范围。 Worker 可以为 message
事件添加事件侦听器,也可以定义 onmessage
处理程序来侦听父线程发送的任何消息。
查找下一个素数的任务显然不是工作人员的理想用例,但在这里选择它是为了演示传递消息的概念。随后,我们确实探索了使用 Web Worker 真正能带来好处的可能且实际的用例。
工人是资源密集型的;它们是操作系统级线程。因此,您不想创建大量工作线程,并且应该在 Web Worker 完成工作后终止它。工人可以终止自己,如下所示:
self.close();
或者父线程可以终止工作线程:
primeWorker.terminate();
在工作脚本中,我们无法访问许多重要的 JavaScript 对象,例如 document
、window
、console
、parent
,最重要的是无法访问 DOM。没有 DOM 访问权限并且无法更新页面确实听起来限制太多,但这是一个重要的安全设计决策。想象一下,如果多个线程尝试更新同一元素,可能会造成严重破坏。因此,网络工作者生活在一个受限且线程安全的环境中。
话虽如此,您仍然可以使用worker来处理数据并将结果返回到主线程,然后主线程可以更新DOM。尽管他们被拒绝访问一些非常重要的 JavaScript 对象,但工作人员可以使用一些函数,例如 setTimeout()/clearTimeout()
、setInterval()/clearInterval()
、navigator
等。还可以在工作器内使用 XMLHttpRequest
和 localStorage
对象。
在工作者上下文中,
self
和this
均指全局范围。
为了与服务器通信,工作人员必须遵循同源策略。例如,托管在 http://www.example.com/
上的脚本无法访问 https://www.example.com/
上的脚本。即使主机名相同,同源策略也规定协议也必须相同。通常,这不是问题。您很可能正在编写工作程序、客户端,并从同一域为它们提供服务,但了解限制总是有用的。
Google Chrome 对本地访问工作程序设置了限制,因此您将无法在本地设置上运行这些示例。如果您想使用 Chrome,则必须在某个服务器上托管这些文件,或者在从命令行启动 Chrome 时使用 --allow-file-access-from-files
标志。对于 OS X,按如下方式启动 chrome:
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files
但是,不建议在生产环境中使用此标志。因此,最好的选择是将这些文件托管在网络服务器上,并在任何支持的浏览器中测试您的网络工作人员。
无法访问 console
使得这有点不简单,但是借助 Chrome 开发工具,人们可以像调试任何其他 JavaScript 代码一样调试工作代码。
要处理 Web Worker 抛出的任何错误,您可以侦听 error
事件,该事件会填充 ErrorEvent 对象。您可以检查该对象以了解错误的详细原因。
primeWorker.addEventListener('error', function(error){ console.log(' Error Caused by worker: '+error.filename + ' at line number: '+error.lineno + ' Detailed Message: '+error.message); });
虽然多个工作线程在它们之间划分工作是很常见的,但需要注意的是。官方规范指定这些工作人员相对重量级,预计将是在后台运行的长期脚本。 Web Worker 不适合大量使用,因为它们的启动性能成本和每个实例的内存成本都很高。
该规范概述了两种类型的工作人员:专用工作人员和共享工作人员。到目前为止,我们已经看到了敬业工人的例子。它们直接链接到其创建者脚本/页面,因为它们与创建它们的脚本/页面具有一对一的关系。另一方面,共享工作人员可以在同一个来源的所有页面之间共享(即:同一来源的所有页面或脚本都可以与共享工作人员通信)。
要创建共享工作线程,只需将脚本的 URL 或工作线程的名称传递给 SharedWorker 构造函数即可。
共享工作程序使用方式的主要区别在于,它们与 port
相关联,以跟踪访问它们的父脚本。
以下代码片段创建一个共享工作线程,注册回调以监听该工作线程发布的任何消息,并将消息发布到共享工作线程:
var sharedWorker = new SharedWorker('findPrime.js'); sharedWorker.port.onmessage = function(event){ ... } sharedWorker.port.postMessage('data you want to send');
类似地,工作人员可以侦听 connect
事件,当新客户端尝试连接到工作人员时会收到该事件,然后相应地向其发送消息。
onconnect = function(event) { // event.source contains the reference to the client's port var clientPort = event.source; // listen for any messages send my this client clientPort.onmessage = function(event) { // event.data contains the message send by client var data = event.data; .... // Post Data after processing clientPort.postMessage('processed data'); } };
由于它们的共享性质,您可以在同一应用程序的不同选项卡中维护相同的状态,因为不同选项卡中的两个页面使用相同的共享工作脚本来维护和报告状态。有关共享工作人员的更多详细信息,我鼓励您阅读规范。
Web Worker 不适合大量使用,因为它们的启动性能成本很高,每个实例的内存成本也很高。
现实生活中的场景可能是,您被迫处理同步第三方 API,该 API 强制主线程在继续执行下一条语句之前等待结果。在这种情况下,您可以将此任务委托给新生成的工作线程,以利用异步功能为您带来好处。
Web 工作人员还擅长轮询情况,在这种情况下,您可以在后台连续轮询目标,并在一些新数据到达时将消息发布到主线程。
您可能还需要处理服务器返回的大量数据。传统上,处理大量数据会对应用程序的响应能力产生负面影响,从而使用户体验变得不可接受。更优雅的解决方案是将处理工作分配给多个工作人员来处理数据的非重叠部分。
其他用例可能是在多个网络工作人员的帮助下分析视频或音频源,每个工作人员都处理问题的预定义部分。
想象一下在单线程环境中与多个线程相关的强大功能。
与 HTML5 规范中的许多内容一样,Web Worker 规范也在不断发展。如果您打算成为网络工作者,那么看看规范不会有什么坏处。
对于使用当前版本的 Chrome、Safari 和 Firefox 的专业工作人员来说,跨浏览器支持相当不错。即使是 IE 也没有落后太多,IE10 占据了主导地位。但是,仅当前版本的 Chrome 和 Safari 支持共享工作线程。令人惊讶的是,Android 4.0 中提供的最新版本的 Android 浏览器不支持 Web Worker,尽管在 2.1 版本中支持了 Web Worker。 Apple 还从 iOS 5.0 开始提供了 Web Worker 支持。
想象一下在单线程环境中与多线程相关的强大功能。可能性是无限的!
以上是從 Web Worker 開始的詳細內容。更多資訊請關注PHP中文網其他相關文章!