前言:一直都以爲只有java才能談多線程這麼高逼格的話題,惋惜java和javascript的關係就比如雷鋒跟雷峯塔的關係,不過在HTML5新特性中有一個webworker,關於它的理論若是沒有相關demo去實踐它可能理解起來會有點僵硬。本文是經過canvas裏面的一個操做像素的demo來更爲直觀地展示webworker的性能。javascript
JavaScript引擎是單線程運行的,JavaScript中耗時的I/O操做都被處理爲異步操做,它們包括鍵盤、鼠標I/O輸入輸出事件、窗口大小的resize事件、定時器(setTimeout、setInterval)事件、Ajax請求網絡I/O回調等。當這些異步任務發生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到JavaScript運行時執行線程空閒時候纔會按照隊列先進先出的原則被一一執行,但終究仍是單線程。只要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比運行一個運算量很是大的函數),致使整個頁面卡在這個地方,其餘任務沒法執行。java
正是由於單線程因此註定了js中的某些操做必須是異步的,舉個粒子:node
window.onload = function () {
console.log(111);
setTimeout(function() {
console.log(222);
}, 0);
console.log(333);
}複製代碼
上面的代碼依次輸出111,333,222,由於運行js碰到屬於異步操做的代碼就會單獨拎出來放在一個隊列(setTimeout就屬於異步操做,不論是不是0秒後執行),等待同步代碼(不屬於異步操做的代碼就是同步了)執行完就依次執行隊列裏的操做。其實在head裏面加載js的話window.onload自己也屬於異步代碼。git
一、利用setTimout或回調實現異步es6
二、動態建立script標籤github
三、利用script提供的defer/asyncweb
四、es6裏的promiseajax
這裏只是總結一下,以爲前三種都很雞肋,用好回調和promise大法就行了。編程
Web Worker 是HTML5標準的一部分,這一規範定義了一套API,它容許一段JavaScript程序運行在主線程以外的另一個線程中。工做線程容許開發人員編寫可以長時間運行而不被用戶所中斷的後臺程序, 去執行事務或者邏輯,並同時保證頁面對用戶的及時響應,能夠將一些大量計算的代碼交給web worker運行而不凍結用戶界面。這是這個標題幾乎標準的答案,好像仍是能夠理解的。canvas
//---------------放在頁面上的main.js:主線程
var worker = new Worker("worker.js");
// 主線程向子線程發送數據
worker.postMessage(data);
worker.onmessage = function(ev){
var data = ev.data;
// 主線程收到子線程的處理後的數據
};
//-----------------------------------//
//-------------放在後臺的worker.js:子線程
self.onmessage = function(ev){
var data = ev.data //收到主線程發過來的源數據
var newdata = handle(data);
self.postMessage(newdata );//把處理好的數據發回去
};
//把一些運算不叫複雜或工做量大的js代碼拎過來
function handle(data){ some coding... }
//----------------------------------//
//-----------------------------中止worker
// 方式一 main.js 在主線程中止方式
var worker = new Worker('./worker.js');
...
worker.terminate();
// 方式2、worker.js
self.close();複製代碼
這裏就先羅列一個最基本的流程語法吧,很是簡單,須要特別注意:子線程裏面的js代碼所支持的語法很是有限,只支持ECMAscript的基本語法,具體範圍是多大呢,咱們都知道javascript大體分爲ECMAscript、DOM、BOM、nodeJs。第一類是基礎,DOM類是基於ECMAscript實現的去操做DOM樹的,BOM類就是瀏覽器行爲語法,而nodeJs則是基於ECMAscript去操做os、file、database、net等等之類的(不知道這樣理解會不會被人打^_^)。因此它只支持像string、array、object...這樣子的東西,連console.log和alert都不支持,相信你們確定知道這個範圍。還有上面說了線程是後臺開的,這些文件天然要放在服務器環境下運行的。在子線程中還能夠經過 importScripts( 'another1.js' ,'another2.js' )來引入其餘的js文件,是否是看到一點原生js模塊化的影子呢,然而這其實沒什麼軟用。
//------------------------------------------------主線程
window.onload = function () {
var oc = document.getElementById("print");
var ogc = oc.getContext('2d');
ogc.fillStyle = "#fc6423";
var ali = document.getElementsByTagName('li');
for (var i = 0; i < ali.length; i++) {
ali[i].onclick = function () {
console.time(1);//------------------計時開始
ogc.clearRect(0, 0, oc.width, oc.height);
ogc.save();
var h = 200;
var str = this.innerHTML;
ogc.font = h * 0.85 + 'px impact';
ogc.textBaseline = 'top';
var w = ogc.measureText(str).width;
ogc.fillText(str, (oc.width - w) / 2, (oc.height - h) / 2);
var allarea = ogc.getImageData((oc.width - w) / 2, (oc.height - h) / 2, w, h);
console.log(w);
ogc.clearRect(0, 0, oc.width, oc.height);
var newarea = ogc.createImageData(w, h);
// var allarr = suiji(w * h, w * h / 10);
//這裏開一個worker,爲的就是把上一行代碼:10組隨機數的產生拎到子線程中去運行
var worker = new Worker('cutarr.js');
worker.postMessage(w*h);// 將數據發給子線程
worker.onmessage = function (ev) {
var allarr = ev.data;//收處處理後的數據
var inow = 0;
var timer = null;
timer = setInterval(function () {
for (var i = 0; i < allarr[inow].length; i++) {
newarea.data[allarr[inow][i] * 4] = allarea.data[allarr[inow][i] * 4];
newarea.data[allarr[inow][i] * 4 + 1] = allarea.data[allarr[inow][i] * 4 + 1];
newarea.data[allarr[inow][i] * 4 + 2] = allarea.data[allarr[inow][i] * 4 + 2];
newarea.data[allarr[inow][i] * 4 + 3] = allarea.data[allarr[inow][i] * 4 + 3];
}
ogc.putImageData(newarea, (oc.width - w) / 2, (oc.height - h) / 2);
if (inow == 9) {
inow = 0;
clearInterval(timer);
}
inow++;
}, 150)
ogc.restore();
}
console.timeEnd(1); //------------------計時結束
}
}
}
//--------------------------------------------------子線程
function suiji(all, part) {
var arr1 = [];
var allarr = [];
for (var i = 0; i < all; i++) {
arr1.push(i);
}
for (var i = 0; i < all / part; i++) {
var newarr = [];
for (var j = 0; j < part; j++) {
newarr.push(arr1.splice(Math.floor(Math.random() * arr1.length), 1));
}
allarr.push(newarr);
}
return allarr;
}
self.onmessage = function (ev) {
var arr = suiji(ev.data,ev.data/10);
self.postMessage(arr);
}複製代碼
上面這段js代碼要完成的是將一塊區域內的近10萬顆像素(可調文字大小改變)每隔150ms隨機顯示其1/10的像素點,最終造成一個完整的漢字,其中最耗時的過程在於隨機數的產生,有幾個像素點就進行幾回random操做。這是由隨機函數suiji來完成,這裏單純地把它拎到子線程中去處理。上面的代碼是用了webworker的,不使用webworker:即把suiji()放在window.onload裏面,直接用 var allarr = suiji(w h, w h / 10)來產生就行了。接下來對比一下兩種請況下從點擊每一個字到打印計時一共花費多少時間:
一個接近2秒,一個10ms之內,這。。。並且前面提到:工做線程容許開發人員編寫可以長時間運行而不被用戶所中斷的後臺程序, 去執行事務或者邏輯,並同時保證頁面對用戶的及時響應,也就是說,當瀏覽器碰到這句代碼:
var allarr = suiji(w * h, w * h / 10);複製代碼
頁面就會卡死近2000ms,不能有任何操做,好比說點擊、右鍵,更不用說執行後面的代碼了:
console.log(111);
var allarr = suiji( w*h,w*h/10 );
console.log(222);複製代碼
這樣我看到:幾乎在點擊的同時就打印了111,而22二、計時和分割線是在近2000ms以後同時打印的,由於上面三行代碼都是同步的,而用了webworker則不存在卡死的請況,速度也快多了。若是面積小看不出有多少差異,能夠若是把大小調到500,前者就達到了恐怖的15秒之多了。但是這時候用了webworker雖然不會形成卡死狀態,但好像也快不了多少,也許在一個可控的範圍內才能發揮它的性能吧,或者說原本js代碼對多線程的支持就很差。你們能夠本身去編寫更復雜變態的函數去測驗。
Web Worker帶來後臺計算能力,WebWorker自身是由webkit多線程實現,但它並無爲Javasctipt語言帶來多線程編程特性,咱們如今仍然不能在Javascript代碼中建立並管理一個線程,或者主動控制線程間的同步與鎖等特性。Web Worker 只是瀏覽器(宿主環境)提供的一個能力/API。並且它不支持IE。
一、使用專用線程進行數學運算
Web Worker最簡單的應用就是用來作後臺計算,而這種計算並不會中斷前臺用戶的操做
二、 圖像處理
經過使用從canvas或者video元素中獲取的數據,能夠把圖像分割成幾個不一樣的區域而且把它們推送給並行的不一樣Workers來作計算
三、大量數據的檢索
當須要在調用 ajax後處理大量的數據,若是處理這些數據所需的時間長短很是重要,能夠在Web Worker中來作這些,避免凍結UI線程。
四、背景數據分析
因爲在使用Web Worker的時候,咱們有更多潛在的CPU可用時間,咱們如今能夠考慮一下JavaScript中的新應用場景。咱們如今能夠考慮一下JavaScript中的新應用場景。例如,咱們能夠想像在不影響UI體驗的狀況下實時處理用戶輸入。利用這樣一種可能,咱們能夠想像一個像Word(Office Web Apps 套裝)同樣的應用:當用戶打字時後臺在詞典中進行查找,幫助用戶自動糾錯等等。