深入理解javascript非同步程式設計障眼法&&h5 web worker實現多執行緒
0.從一道題說起
var t = true;
setTimeout(function(){ t = false; }, 1000);
while(t){ }
alert('end');
問,以上程式碼何時alert“end”呢?
測試一下:答案是:永遠都不會alert。
解析:javascript引擎是單執行緒的,事件觸發排隊等候。所有任務按照觸發時間先後排隊處理。
上例中,排隊的順序狀態是:
| var t=true ; | while(t){}; | alert(‘end’); |
在1000ms之後,setTimeout函式也加入佇列。
while(t){ }無限迴圈阻塞了單執行緒,不管排到後面的程式碼執行時間有多短,後面的程式碼無法執行,一直阻塞下去。
1.瀏覽器執行緒
瀏覽器有這麼幾大執行緒:UI渲染執行緒(用於頁面的渲染),javascript引擎執行緒(用於處理js),GUI事件觸發執行緒(用於互動)。
有時會開啟的執行緒:http傳輸執行緒,定時觸發執行緒(定時器)
它們之間的關係是什麼呢?
(1)UI渲染執行緒 與 javascript引擎執行緒 互斥
由於javascript可以操縱頁面的DOM,所以如果UI渲染執行緒與javascript引擎執行緒 不互斥的話,在UI渲染執行緒進行頁面渲染的同時,javascript引擎執行緒進行DOM修改,最終會造成DOM狀態不一致的現象。所以,當javascript引擎執行緒執行的時候,UI渲染執行緒處於凍結狀態。
(2)javascript引擎執行緒 與 GUI事件觸發執行緒(用於互動) 非同步
瀏覽器開啟事件觸發執行緒,等待使用者動作,事件觸發執行緒解析為響應事件,轉移到javascript引擎執行緒,排隊等候,等待javascript引擎的處理。
(3)javascript引擎執行緒 與 http傳輸執行緒 非同步
網頁get,post等請求,xhr非同步請求都通過http傳輸執行緒,傳送到javascript引擎排隊,等候處理。
(4)javascript引擎執行緒 與 定時觸發執行緒(定時器) 非同步
setTimeout(),setInterval()由單獨的執行緒 定時觸發執行緒 觸發,傳送到javascript引擎排隊等候,等待處理。
2.xhr非同步是障眼法
我們來做一個試驗:
客戶端js程式碼
//jquery封裝的ajax請求,請求http://localhost:3000/login頁面
$.ajax({
type: "post",
url: "http://localhost:3000/login",
dataType: "json",
data:{ username: username, password: password },
success: function(data){
if(data){
if(data.message=="i202"){
alert('密碼錯誤,請重新輸入');
window.location.href="login";
}else if(data.message=="i200"){
alert('登陸成功');
window.location.href="index";
}
else{
alert('沒有這個使用者名稱');
window.location.href="login";
}
} else{
}
}
});
//這裡有一個無限迴圈
while(1){}
後端node.js程式碼:
//後臺對post的響應
router.post('/login', function (req, res, next) {
sleep(1000);
res.send({status:"success", message:"i200"});
});
/**
* 模擬sleep
* @param sleepTime
*/
function sleep(sleepTime) {
for(var start = +new Date; +new Date - start <= sleepTime; ) { }
}
前臺將永遠不會alert(“登陸成功”)。瀏覽器通過http執行緒收到xhr響應,但是轉到javascript執行緒等待執行。javascript單執行緒,一次只能處理一個任務。第一個任務無限迴圈,後面的任務全部阻塞。
xhr非同步程式設計實際上是一種障眼法。
3.定時器時間不準
(1)時間不準1
setTimeout(function () { while (true) { } }, 1000);
setTimeout(function () { alert('end 2'); }, 2000);
setTimeout(function () { alert('end 1'); }, 100);
alert('end');
執行這段程式碼。執行結果是alert(‘end’) alert(‘end 1’)。
前兩個定時器並不能如約在規定的時間點執行哦。
(2)時間不準2
setTimeout(function(){
/* 程式碼塊... */
setTimeout(arguments.callee, 10);
}, 10);
setInterval(function(){
/*程式碼塊... */
}, 10);
兩個定時器,本想實現相同的功能:每十秒觸發一次定時器。
但是實際上,setTimeout在10ms後才加入js執行佇列,排隊等待。所以每兩次定時器觸發的時間間隔可能 > 10ms。
setInterval每10s就向js執行佇列新增一個setInterval事件等待執行。前面的setInterval事件可能被它之前的事件阻塞,導致執行晚了幾拍。那麼沒兩次定時時間觸發的時間間隔可能 <10ms 。
3.web worker 才是真正多執行緒
來吧,試驗一下:
index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="js/Fthread.js"></script>
</body>
</html>
Fthread.js
//這裡建立一個webworker就是開一個新的執行緒
var worker=new Worker('js/Sthread.js');//建立子執行緒
//這裡接收新的執行緒傳來的data
worker.onmessage = function(event) {
console.log(event.data);
};
//這個將會觸發向子程序的請求
worker.postMessage("begin");
//構造一個無限迴圈
setTimeout(function () { while (true) { } }, 1000);
Sthread.js
//這裡佔有一個新執行緒,向主執行緒傳送訊息
postMessage('hello');
//實現之前的一個例項,看是否阻塞
setTimeout(function () { console.log('end 2'); }, 2000);
setTimeout(function () { console.log('end 1'); }, 100);
console.log('end');
執行結果:
end
hello //這是兩個執行緒資料的傳送,可以不看
end1
哈哈,end1沒有被阻塞,因為人家是在子執行緒執行滴。這才是多執行緒嘛。
看到某大大在部落格裡說可以使用webworker開執行緒給後臺傳送實時心跳。告訴後臺前臺沒崩。不過開一個執行緒據說開銷很大。這就。。。