客戶端javascript其中一個基本的特性就是單線程:好比,瀏覽器沒法同時運行兩個事件處理程序,它也沒法在一個事件處理程序運行的時候觸發一個計時器。Web Worker是HTML5提供的一個javascript多線程解決方案,能夠將一些大計算量的代碼交由web Worker運行從而避免阻塞用戶界面,在執行復雜計算和數據處理時,這個API很是有用。本文將詳細介紹Web Workerjavascript
在使用Worker以前,首先要檢測瀏覽器是否支持這個APIhtml
[注意]IE9-瀏覽器不支持前端
if (window.Worker) { // to do }
使用Web Worker有如下幾點限制:java
同域限制。子線程加載的腳本文件,必須與主線程的腳本文件在同一個域web
DOM限制。子線程所在的全局對象,與主進程不同,它沒法讀取網頁的DOM對象,即document、window、parent這些對象,子線程都沒法獲得。(可是,navigator對象和location對象能夠得到。)chrome
腳本限制。子線程沒法讀取網頁的全局變量和函數,也不能執行alert和confirm方法,不過能夠執行setInterval和setTimeout,以及使用XMLHttpRequest對象發出AJAX請求瀏覽器
文件限制。子線程沒法讀取本地文件,即子線程沒法打開本機的文件系統(file://),它所加載的腳本,必須來自網絡網絡
【新建】多線程
主線程採用new命令,調用Worker構造函數,能夠新建一個子線程app
var worker = new Worker('work.js');
Worker構造函數的參數是一個腳本文件,這個文件就是子線程所要完成的任務,上面代碼中是work.js
這行代碼會致使瀏覽器下載work.js,但只有Worker接收到消息纔會實際執行文件中的代碼
因爲子線程不能讀取本地文件系統,因此這個腳本文件必須來自網絡端。若是下載沒有成功,好比出現404錯誤,這個子線程就會默默地失敗
【傳遞消息】
要給Worker傳遞消息,可使用postMessage()方法。postMessage()方法的參數,就是主線程傳給子線程的信號。它能夠是一個字符串,也能夠是一個對象
通常來講,能夠序列化爲JSON結構的任何值均可以做爲參數傳遞給postMessage()。換句話說,這就意味着傳入的值是被複制到Worker中,而非直接傳過去的
worker.postMessage("Hello World");
worker.postMessage({method: 'echo', args: ['Work']});
只要符合父線程的同源政策,Worker線程本身也能新建Worker線程。Worker線程可使用XMLHttpRequest進行網絡I/O,可是XMLHttpRequest對象的responseXML和channel屬性老是返回null
【事件監聽】
主線程使用onmessage事件來監聽子線程發來的信息。來自子線程Worker的數據保存在event.data中。Worker返回的數據也能夠是任何可以被序列化的值
worker.addEventListener('message', function(e) { //對數據進行處理 console.log(e.data); }, false);
【錯誤處理】
若是子線程發生錯誤,會觸發主線程的error事件。error事件的事件對象中包含三個屬性:filename、lineno和message,分別表示發生錯誤的文件名、代碼行號和完整的錯誤消息
worker.onerror = function(event){ console.log("ERROR:"+event.filename+"("+event.lineno+"):"+event.message); };
建議在使用Web Worker時,始終都要使用onerror事件處理程序,即便這個函數除了把錯誤記錄到日誌中什麼也不作均可以。不然,Worker就會在發生錯誤時,悄無聲息地失敗了
【中止子線程】
任什麼時候候,只要調用terminate()方法就能夠中止Worker的工做。並且,Worker中的代碼會當即中止執行,後續的全部過程都不會再發生(包括error和message事件也不會再觸發)
worker.terminate();//當即中止 Worker 的工做
【做用域】
關於子線程Web Worker,最重要的是要知道它所執行的javascript代碼徹底在另外一個做用域中,與當前網頁中的代碼不共享做用域。在Web Worker中,一樣有一個全局對象和其餘對象以及方法
Web Worker中的全局對象是worker對象自己。也就是說,在這個特殊的全局做用域中,this和self引用的都是worker對象。爲便於處理數據,Web Worker自己也是一個最小化的運行環境,包括如下:
一、最小化的navigator對象,包括onLine、appName、appVersion、userAgent和platform屬性
二、只讀的location對象
三、setTimeout()、setInterval()、clearTimeout()和clearInterval()方法
四、XMLHttpReguest構造函數
顯然,Web Worker的運行環境與頁面環境相比,功能是至關有限的
【事件監聽】
當主線程在worker對象上調用postMessage()時,數據會以異步方式被傳遞給子線程worker,進而觸發子線程worker中的message事件。爲了處理來自頁面的數據,一樣也須要建立一個onmessage事件處理程序
self表明子線程自身,self.addEventListener表示對子線程的message事件指定回調函數(直接指定onmessage屬性的值也可)。回調函數的參數是一個事件對象,它的data屬性包含主線程發來的信號
/* File: work.js */ self.addEventListener('message', function(e){ var data = event.data; //處理數據 }, false);
【傳遞消息】
子線程使用postMessage()方法向主線程傳遞信息
/* File: work.js */ self.onmessage = function(e) { var data = e.data; //處理數據 var method = data.method; var args = data.args; var reply = doSomething(args); self.postMessage({method: method, reply: reply}); };
【包含腳本】
沒法在子線程Worker中動態建立新的<script>元素,但能夠調用importScripts()方法來向Worker中添加其餘腳本。這個方法接收一個或多個指向javascript文件的URL。每一個加載過程都是異步進行的,所以全部腳本加載並執行以後,importScripts()纔會執行
/* File: work.js */ importScripts("file1.js", "file2.js");
即便file2.js先於file1.js下載完,執行的時候仍然會按照前後順序執行。並且,這些腳本是在子線程Worker的全局做用域中執行,若是腳本中包含與頁面有關的javascript代碼,那麼腳本可能沒法正確運行。請記住,Worker中的腳本通常都具備特殊的用途,不會像頁面中的腳本那麼功能寬泛
【中止子線程】
在子線程Worker中,調用close()方法也能夠中止工做。就像在主線程中調用terminate()方法同樣,子線程Worker中止工做後就不會再有事件發生了
/* File: work.js */ self.close();
一般狀況下,子線程載入的是一個單獨的javascript文件,可是也能夠載入與主線程在同一個網頁的代碼
假設網頁代碼以下:
<!DOCTYPE html> <body> <script id="worker" type="app/worker"> addEventListener('message', function() { postMessage('Im reading Tech.pro'); }, false); </script> </body> </html>
咱們能夠讀取頁面中的script,用worker來處理
var blob = new Blob([document.querySelector('#worker').textContent]);
這裏須要把代碼看成二進制對象讀取,因此使用Blob接口。而後,這個二進制對象轉爲URL,再經過這個URL建立worker
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
部署事件監聽代碼
worker.addEventListener('message', function(e) {
console.log(e.data);
}, false);
最後,啓動worker
worker.postMessage('');
整個頁面的代碼以下:
<!DOCTYPE html> <body> <script id="worker" type="app/worker"> addEventListener('message', function() { postMessage('Work done!'); }, false); </script> <script> (function() { var blob = new Blob([document.querySelector('#worker').textContent]); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); worker.addEventListener('message', function(e) { console.log(e.data); }, false); worker.postMessage(''); })(); </script> </body> </html>
能夠看到,主線程和子線程的代碼都在同一個網頁上面
上面的段落介紹瞭如何使用Web Worker,那麼它到底有什麼用,能夠幫咱們解決那些實現問題呢?
Web Worker的一個優點在於可以執行處理器密集型的運算而不會阻塞UI線程,下面來看一個斐波那契(fibonacci)數列的例子
在數學上,fibonacci數列被以遞歸的方法定義:
F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
javascript的經常使用實現爲:
var fibonacci = function(n) { return n <2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); };
在chrome中用該方法進行40的fibonacci數列執行時間爲13084.775毫秒,因爲javascript是單線程執行的,在求數列的過程當中瀏覽器不能執行其它javascript腳本,UI渲染線程也會被掛起,從而致使瀏覽器進入僵死狀態
<script> var fibonacci = function(n) { return n <2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); }; var t0 = window.performance.now(); fibonacci(40); var t1 = window.performance.now(); //fibonacci函數執行了13084.775毫秒 console.log("fibonacci函數執行了" + (t1 - t0) + "毫秒") </script>
使用web worker將數列的計算過程放入一個新線程裏去執行將避免這種狀況的出現
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>web worker fibonacci</title> </head> <body> <script> if(window.Worker){ var worker = new Worker('fibonacci.js'); worker.onmessage = function(e){ var t1 = window.performance.now(); //fibonacci函數執行了11741.900000000001毫秒 console.log("fibonacci函數執行了" + (t1 - t0) + "毫秒") } worker.onerror = function(e){ console.log("ERROR:"+e.filename+"("+e.lineno+"):"+e.message); }; worker.postMessage(40); var t0 = window.performance.now(); } </script> </body> </html>
//fibonacci.js var fibonacci =function(n) { return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); }; self.onmessage = function(e) { self.postMessage(fibonacci(e.data)); };
雖然fibonacci數列的計算時間並無縮短多少,但因爲其徹底在本身獨立的線程中計算,只是在計算完成以後將結果發回主線程。因此並不會影響到主線程的代碼執行
所以,利用web worker咱們能夠在前端執行一些複雜的大量運算而不會影響頁面的展現