Web Worker

前面的話

  客戶端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咱們能夠在前端執行一些複雜的大量運算而不會影響頁面的展現

相關文章
相關標籤/搜索