1. 程式人生 > >Web Worker

Web Worker

代碼 eno rom 線程加載 都在 lec lob 打開 argument

前面的話

  客戶端javascript其中一個基本的特性就是單線程:比如,瀏覽器無法同時運行兩個事件處理程序,它也無法在一個事件處理程序運行的時候觸發一個計時器。Web Worker是HTML5提供的一個javascript多線程解決方案,可以將一些大計算量的代碼交由web Worker運行從而避免阻塞用戶界面,在執行復雜計算和數據處理時,這個API非常有用。本文將詳細介紹Web Worker

前提

  在使用Worker之前,首先要檢測瀏覽器是否支持這個API

  [註意]IE9-瀏覽器不支持

if (window.Worker) {
  // to do 
} 

  使用Web Worker有以下幾點限制:

  同域限制。子線程加載的腳本文件,必須與主線程的腳本文件在同一個域

  DOM限制。子線程所在的全局對象,與主進程不一樣,它無法讀取網頁的DOM對象,即document、window、parent這些對象,子線程都無法得到。(但是,navigator對象和location對象可以獲得。)

  腳本限制。子線程無法讀取網頁的全局變量和函數,也不能執行alert和confirm方法,不過可以執行setInterval和setTimeout,以及使用XMLHttpRequest對象發出AJAX請求

  文件限制。子線程無法讀取本地文件,即子線程無法打開本機的文件系統(file://),它所加載的腳本,必須來自網絡

主線程

【新建】

  主線程采用new命令,調用Worker構造函數,可以新建一個子線程

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本身也是一個最小化的運行環境,包括以下:

  1、最小化的navigator對象,包括onLine、appName、appVersion、userAgent和platform屬性

  2、只讀的location對象

  3、setTimeout()、setInterval()、clearTimeout()和clearInterval()方法

  4、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我們可以在前端執行一些復雜的大量運算而不會影響頁面的展示

Web Worker