為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。
為了理解Web Worker是如何工作的,舉個栗子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>worker</title>
</head>
<body>
<input type="text" name="ipt" id="ipt" value="" />
<button id="start">start</button>
<button id="stop">stop</button>
<button id="ale">alert</button>
<script type="text/javascript">
var ipt = document.getElementById("ipt");
var stop = document.getElementById("stop");
var start = document.getElementById("start");
var ale = document.getElementById("ale");
var worker = new Worker("test22.js");
worker.onmessage = function(){
ipt.value = event.data;
};
stop.addEventListener("click",function(){
//用于關閉worker線程
worker.terminate();
});
start.addEventListener("click",function(){
//開起worker線程
worker = new Worker("test22.js");
});
ale.addEventListener("click",function(){ //點擊事件會插入主線程
alert("i'm a dialog"); //alert阻塞了主線程
console.log("waiting");
setTimeout(function(){
console.log("waited for 5 seconds");
},5000)
});
var aa = setTimeout(function(){
console.log("Is 5 seconds.");
},5000)
var index = 0;
var bb = setInterval(function(){
index++;
console.log(index);
},1000)
</script>
</body>
</html>
下面是test22.js里的代碼,也就是存在于worker線程里的代碼
var i = 0;
function mainFunc(){
i++;
//把i發送到瀏覽器的js引擎線程里
postMessage(i);
}
var id = setInterval(mainFunc,1000);
運行起來我們會發現
點擊確定后,它的數值并非2,而是一個比2更大的數
雖然dialog的彈出會阻塞js引擎線程,但是并不影響worker線程的運行,所以,在我們點擊確定后,只是在js引擎線程上更新了新的內容,而數值是一直在跑動的,這就說明worker線程和原本的js線程互不影響。
那么既然互不影響,兩個線程之間要怎么來聯系呢,答案其實已經在代碼里了,那就是onPostMessage
和 onmessage
這兩個函數,其中onPostMessage(data)
的參數是你要傳遞的數據,而onmessage
是一個回調函數,只有在接受到數據時,onmessage
會被回調,onmessage
有一個隱藏的參數,那就是event
,我們可以用event.data
獲取到傳遞過來的數據來更新主線程。
參考文檔:HTML5 Web Workers
栗子中,aa
在bb
前面,所以輸出aa
也就在bb
前面;如果它們位置對調,結果也將相反。
設定一個5000ms后執行的定時器不代表代碼會在5000ms之后執行,而是指代碼會在5000ms后加入到代碼隊列中。只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重復。
當循環到5000ms時,aa
先加入,所以先執行。
如果將setTimeout()
的第二個參數設為0
,就表示當前代碼執行完(執行棧清空)以后,立即執行(0毫秒間隔)指定的回調函數。
setTimeout(function(){
console.log(1);
}, 0);
console.log(2);
上面代碼的執行結果總是2,1
,因為只有在執行完第二行以后,系統才會去執行"任務隊列"中的回調函數。
下面我們來看一個很有意思的面試題:
實現一個LazyMan,可以按照以下方式調用:
LazyMan(“Hank”)輸出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)輸出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)輸出
Wake up after 5
//等待5秒
Hi This is Hank!
Eat supper
以此類推。
這道題目是典型的考JavaScript流程控制,參考答案如下:
var _LazyMan = function(name){
this.tasks = [];
var self = this;
var fn =(function(n){
var name = n;
return function(){
console.log("Hi! This is " + name + "!");
self.next();
}
})(name);
this.tasks.push(fn);
setTimeout(function(){
//console.log(this); //這里的this指向window
console.log(self.tasks); //任務隊列開始執行時,已經排好隊列。
self.next(); //開始執行第一個
}, 0); // 在下一個事件循環啟動任務
}
_LazyMan.prototype.next = function() {
var fn = this.tasks.shift(); //去掉事件流第一個事件,并返回第一個事件
fn && fn(); //執行這個事件
}
_LazyMan.prototype.eat = function(timer){
var self = this;
var fn =(function(name){
return function(){
console.log("Eat " + name + "~");
self.next() //執行下一個
}
})(name);
this.tasks.push(fn);
return this; // 實現鏈式調用
}
_LazyMan.prototype.sleep = function(time){
var self = this;
var fn = (function(time){
return function(){
console.log("Wake up after " + time + "s!");
setTimeout(function(){
self.next();
},time*1000);
}
})(time);
this.tasks.push(fn);
return this;
}
_LazyMan.prototype.sleepFirst = function(time){
var self = this;
var fn = (function(time){
return function(){
console.log("Wake up after " + time + "s!");
setTimeout(function(){
self.next();
},time*1000);
}
})(time);
this.tasks.unshift(fn);
return this;
}
function LazyMan(name){
return new _LazyMan(name);
}
//test
LazyMan('Hank').sleepFirst(3).eat('supper').sleep(5).eat("dinner");
//這一長段都在主線程 ,返回的是一個實例化的 LazyMan 對象,事件已經排好隊了
參考文獻: