在web開發的時候經常會遇到浏覽器不響應事件進入假死狀態,甚至彈出“腳本運行時間過長“的提示框,如果出現這種情況說明你的腳本已經失控了。
一個浏覽器至少存在三個線程:js引擎線程(處理JS)、GUI渲染線程(渲染頁面)、浏覽器事件觸發線程(控制交互)。
JavaScript引擎是基於事件驅動單線程執行的,JS引擎一直等待著任務隊列中任務的到來然後加以處理,浏覽器無論再什麼時候都只有一個JS線程在運行JS程序。
GUI 渲染線程負責渲染浏覽器界面,當界面需要重繪(Repaint)或由於某種操作引發回流(reflow)時,該線程就會執行。但需要注意 GUI渲染線程與JS引擎是互斥的,當JS引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JS引擎空閒時立即被執行。
事件觸發線程,當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeOut、也可來自浏覽器內核的其他線程如鼠標點擊、AJax異步請求等,但由於JS的單線程關系所有這些事件都得排隊等待JS引擎處理。
了解了浏覽器的內核處理方式就不難理解浏覽器為什麼會進入假死狀態了,當一段JS腳本長時間占用著處理機就會掛起浏覽器的GUI更新,而後面的事件響應 也被排在隊列中得不到處理,從而造成了浏覽器被鎖定進入假死狀態。另外JS腳本中進行了DOM操作,一旦JS調用結束就會馬上進行一次GUI渲染,然後才 開始執行下一個任務,所以JS中大量的DOM操作也會導致事件響應緩慢甚至真正卡死浏覽器,如在IE6下一次插入大量的Html。而如果真的彈出了“腳本 運行時間過長“的提示框則說明你的JS腳本肯定有死循環或者進行過深的遞歸操作了。
現在如果遇到了這種情況,我們可以做的不僅僅是優化 代碼,Html5的webWorkers提供了js的後台處理線程的API,它允許將復雜耗時的單純js邏輯處理放在浏覽器後台線程中進行處理,讓JS線 程不阻塞UI線程的渲染。這個線程不能和頁面進行交互,如獲取元素、alert等。多個線程間也是可以通過相同的方法進行數據傳遞。
直接看代碼:
例子:用戶輸入一個數字,進行加法運算(+=)
以前的做法:
<!DOCTYPE Html>
<Html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>webworkers–calculate</title></head>
<body>
<input id=”num” name=”num” type=”text”/>
<button onclick = “calculate()”>計算</button><br />
<div id=”result” style=”color:red;”></div>
<div id=”time” style=”color:red;”></div>
<script type=”text/Javascript” src=”calculate.JS”></script>
<script type=”text/Javascript”> function calculate(){
data1 = new Date().getTime();
var num = document.getElementById(“num”).value;
var val = parseInt(num,10);
var result =0;
for(var i =0; i<num;i++){
result += i;
}
data2 = new Date().getTime();
document.getElementById(“result”).innerHtml =”計算結果:”+result;
document.getElementById(“time”).innerHtml =”普通 耗時:”+ (data2 – data1)+”ms”;
}
</script>
</body>
</Html>
使用webWorkers以後:
calculate.Html
<!DOCTYPE Html>
<Html lang=”en”><head>
<meta charset=”UTF-8″>
<title>webworkers–calculate</title>
</head>
<body>
<input id=”num” name=”num” type=”text”/>
<button onclick = “calculate()”>計算</button><br />
<div id=”result” style=”color:red;”></div>
<div id=”time” style=”color:red;”></div>
<script type=”text/Javascript” src=”calculate.JS”></script>
<script type=”text/Javascript”>
var worker = new Worker(“calculate.JS”);
var data1 =0;
var data2 =0;
worker.onmessage = function(event){
var data = event.data;
data2 = new Date().getTime();
document.getElementById(“result”).innerHtml =”計算結果:”+data;
document.getElementById(“time”).innerHtml =”workers 耗時:”+ (data2 – data1)+”ms”;
};
function calculate(){
data1 = new Date().getTime();
var num = document.getElementById(“num”).value;
var val = parseInt(num,10);
worker.postMessage(val);
}
</script>
</body>
</Html>
calculate.JS
onmessage = function(event){
var num = event.data;
var result = 0;
for(var i = 0; i<num;i++){
result += i;
}
postMessage(result);
};
webWorker需要將代碼放入web服務器中, 如果使用的是localhost請用高版本的Chrome浏覽器打開,Firefox浏覽器在處理localhost的時候會出現“Could not get domain!”的錯誤,關於這個可以參考:https://bugzilla.mozilla.org/show_bug.CGI?id=682450 對比上面的兩種實現方式,當計算值達到100億的時候,普通做法耗時已經很長,且一般會卡死了。
▲webWorkers在Chrome15下的效果
更正:getTime()返回的應該是毫秒(ms),而不是秒(s)。
如下圖所示:
▲普通方法在Chrome15下的效果
可見webWorkers在未來的web應用中還是非常有價值的。