單線程JavaScript實現(xiàn)異步過程詳解
前兩天硬著頭皮在部門內(nèi)部做了一次技術(shù)分享,主題如題。索性整理成文章留個紀(jì)念!
要了解異步實現(xiàn),首先我們得先了解:
同步 & 異步
同步:會逐行執(zhí)行代碼,會對后續(xù)代碼造成阻塞,直至代碼接收到預(yù)期的結(jié)果之后,才會繼續(xù)向下執(zhí)行任務(wù)。
異步:調(diào)用之后先不管結(jié)果,繼續(xù)向下執(zhí)行任務(wù)。
網(wǎng)上各種文章對同步和異步的解釋也不外如是,但是看文字總是有點晦澀難懂!我就生活化的來比擬一下這兩個概念吧!
就好比請人吃飯:
比如你要請兩個人吃飯,一個是巴菲特,由于他是舉世矚目股神想請他吃飯的人從這里排到了法國,你為表誠意,你會精心打扮自己,請他跟你吃頓特色菜...那么為了請他吃個烤腰子,你全程都在為些事費心費力,投入大量的精力!
所以,也就阻塞了你干別的事情,是的,這就是同步!
請人吃頓飯就這么難嗎?當(dāng)然,也沒有那么難!不信,你請我吃飯試試:
如果你想請我吃飯,那你只需要打個電話通知我一聲:喂,今天晚上請你吃個海底撈?。∥遥汉冒。∪缓竽悴灰獊斫游?,到了點我自己去了!期間,你該干嘛就去干嘛!
看,其他也很簡單嘛?瞧,這就是異步!
那么回到代碼層面:
同步代碼:(代碼片段1)
function someTime() {
let s = Date.now();
while(true) {
if (Date.now() - s > 2000) {
console.log(2)
break;
}
}
}
console.log(1);
someTime();
console.log(3);
// 其打印順序:1 ...(2秒以后)... 2 3異步代碼:(代碼片段2)
function someTime() {
setTimeout(() => {
console.log(2);
}, 2000)
}
console.log(1);
someTime();
console.log(3);
// 其打印順序:1 3 ...(2秒以后)... 2看看,同步代碼,當(dāng)執(zhí)行這種耗時操作時,就會停在原地,一定要等待這時間過去之后才會執(zhí)行后面的代碼!而異步代碼,后面的執(zhí)行完全不受影響...
JavaScript單線程
眾所周知JavaScript是單線程的,所謂單線程是指程序執(zhí)行時,所走的程序路徑按照連續(xù)順序排下來,前面的必須處理好,后面的才會執(zhí)行!這個解釋跟【同步】的解釋如出一轍!
如此看起來異步編程對于單線程而言似乎并非正統(tǒng),甚至有點矛盾。然而,通過剛才的例子,我們發(fā)現(xiàn),JavaScript是真的實現(xiàn)了異步編程的!為啥加了個setTimeout()不能不阻塞了呢?按單線程的執(zhí)行的話那如下代碼會是怎么樣的呢?
function timeOut() {
setTimeout(() => {
console.log('timeOut');
}, 0)
}
function someTime() {
let s = Date.now();
while(true) {
if (Date.now() - s > 2000) {
console.log('some Time')
break;
}
}
}
console.log(1);
timeOut();
someTime();
console.log(3);如果是以單線程那種解釋來執(zhí)行的話,這個打印順序應(yīng)該是:1 - time Out - some Time - 3才對!然而,其真正的執(zhí)行結(jié)果卻是:1 - some Time - 3 - time Out
為什么?瀏覽器的多線程
JavaScript是腳本語言,它需要在一個宿主環(huán)境里才能運(yùn)行,顯然我們接觸較多的宿主環(huán)境就是--瀏覽器!雖說JavaScript是單線程的,然而瀏覽器卻不是!

如圖所求,JavaScript引擎線程稱為主線程,它負(fù)責(zé)解析JavaScript代碼;其他可以稱為輔助線程,這些輔助線程便是JavaScript實現(xiàn)異步的關(guān)鍵了!
如(代碼片段2):主線程負(fù)責(zé)自上而下順序執(zhí)行,當(dāng)遇到setTimeout函數(shù)后,便將其交給定時器線程去執(zhí)行,自己繼續(xù)執(zhí)行下面的代碼!從而達(dá)到異步的目的。
不僅如此,更關(guān)鍵的是:

任務(wù)隊列
當(dāng)定時器線程計時執(zhí)行完之后,會將回調(diào)函數(shù)放入任務(wù)隊列中!
當(dāng)這些任務(wù)加入到任務(wù)隊列后并不會立即執(zhí)行,而是處于等候狀態(tài)!等主線程處理完了自己的事情后,才來執(zhí)行任務(wù)隊列中任務(wù)!
這個過程我感覺像是古代嬪妃被翻了牌子后,就需要在自己寢宮里精心準(zhǔn)備,等待皇上批完湊折后的駕臨...(哦,別想歪了!)
宏任務(wù) & 微任務(wù)
然而,異步任務(wù)卻又分為兩種:一種叫“宏任務(wù)”(MacroTask 或者 Task),一種叫“微任務(wù)”(MicroTask)!
這又是兩個啥玩意呢?

光看這個依然晦澀難懂,那我們來看一段代碼吧!
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);這段代碼的執(zhí)行結(jié)果:1 - 4 - 3 - 2。LOOK!2是最后打印的,哪怕該計時器的時間設(shè)置為0。通過之前的同步和異步的解釋,1和4先于2打印應(yīng)該很好理解了,但同樣是異步,3也優(yōu)先于2打印,這又是為什么呢?答案就是因為 setTimeout屬于宏任務(wù),而Promise屬于微任務(wù)!
好吧~ 這就是宏任務(wù)和微任務(wù)的差別...什么?沒懂?
微任務(wù)是皇后所生的,是嫡子;而宏任務(wù)是某個小妃子所生, 是庶子!你說選太子的時候誰優(yōu)先?
瀏覽器的Event Loop
1.執(zhí)行全局Script同步代碼,形成一個執(zhí)行棧;
2.在執(zhí)行代碼時當(dāng)遇到如上異步任務(wù)時便會按上文所描述的將宏任務(wù)回調(diào)加入宏任務(wù)隊列,微任務(wù)回調(diào)加入微任務(wù)隊列;
3.然而,回調(diào)函數(shù)放入任務(wù)隊列后也不是立即執(zhí)行;會等待執(zhí)行棧中的同步任務(wù)全部執(zhí)行完清空了棧后引擎才能會去任務(wù)隊列檢查是否有任務(wù),如果有那便會將這些任務(wù)加入執(zhí)行棧,然后執(zhí)行!
4.執(zhí)行棧清空后,會先去檢查微任務(wù)隊列是否有任務(wù),逐一將其任務(wù)加入執(zhí)行棧中執(zhí)行,期間如果又產(chǎn)生了微任務(wù)那繼續(xù)將其加入到列隊末尾,并在本周期內(nèi)執(zhí)行完,直到微任務(wù)隊列的任務(wù)全部 清空,執(zhí)行棧也清空后,再去檢查宏任務(wù)隊列是否有任務(wù),取到隊列隊頭的任務(wù)放入到執(zhí)行棧中執(zhí)行,其他可能又會產(chǎn)生微任務(wù),那當(dāng)本次執(zhí)行棧中的任務(wù)結(jié)果清空后又會去檢查微任務(wù)隊列...
5.引擎會循環(huán)執(zhí)行如上步驟,這就是Event Loop!

又要上代碼了:
console.log('start');
setTimeout(() => {
console.log('time1');
Pormise.resolve().then(() => {
console.log('promise1');
})
}, 0);
setTimeout(() => {
console.log('time2');
Pormise.resolve().then(() => {
console.log('promise2');
})
}, 0);
Pormise.resolve().then(() => {
console.log('promise3');
});
console.log('end');這段代碼的打印順序:
start - end - promise3 - timer1 - promise1 - timer2 - promise2
據(jù)說:node 10.x版本上面的輸入結(jié)果會是:
start - end - promise3 - timer1 - timer2 - promise1 - promise2
node 11.x版本以后改了,輸出跟瀏覽器輸出一致了!
Web Worker
HTML5中支持了Web Worker,使得能夠同時執(zhí)行兩段JS了,那是不是就是說JS實現(xiàn)了“多線程”了呢?我們來看看Web Worker的官方解釋:
通過使用Web Workers,Web應(yīng)用程序可以在獨立于主線程的后臺線程中,運(yùn)行一個腳本操作。這樣做的好處是可以在獨立線程中執(zhí)行費時的處理任務(wù),從而允許主線程(通常是UI線程)不會因此被阻塞/放慢。
獨立線程,看似像是實現(xiàn)了“多線程”,然而他是獨立于主線程,也就是主線程依然是那個主線程沒有變!雖然你大媽已經(jīng)不是你大媽了,但是你大爺還是你大爺!JS單線程的本質(zhì)依然沒有變!
WebWorker是向瀏覽器申請一個子線程,該子線程服務(wù)于主線程,完全受主線程控制。
Web Worker注意事項:

寫了一個demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Worker</title>
</head>
<body>
<button onclick="startWorker()">開始</button>
<button onclick="stopWorker()">停止</button>
<button onclick="updateNum()">在運(yùn)行時點擊</button>
<div id="output"></div>
<div id="num"></div>
<script id="worker" type="app/worker">
function updateSync() {
for (let i = 0; i < 10000000000; i++) {
if (i % 100000 === 0) {
postMessage(i);
}
}
}
updateSync();
</script>
<script>
let worker;
function startWorker() {
let blob = new Blob([document.querySelector('#worker').textContent]);
let url = window.URL.createObjectURL(blob);
console.log(url);
worker = new Worker(url);
worker.onmessage = function(e) {
document.getElementById('output').innerHTML = e.data;
}
}
function stopWorker() {
if (worker) {
worker.terminate();
}
}
let num = 0;
function updateNum() {
num++;
document.getElementById('num').innerHTML = num;
}
</script>
</body>
</html>這段代碼可以稍微解釋一下Web Worker的用途之一 --執(zhí)行費時的處理任務(wù)吧!
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Axios?get?post請求傳遞參數(shù)的實現(xiàn)代碼
axios是基于promise用于瀏覽器和node.js的http客戶端,支持瀏覽器和node.js,能攔截請求和響應(yīng),這篇文章主要介紹了axios?get?post請求傳遞參數(shù)的操作代碼,需要的朋友可以參考下2022-11-11
javascript 動態(tài)改變層的Z-INDEX的代碼style.zIndex
javascript 動態(tài)改變層的Z-INDEX的代碼style.zIndex...2007-08-08
JS基于開關(guān)思想實現(xiàn)的數(shù)組去重功能【案例】
這篇文章主要介紹了JS基于開關(guān)思想實現(xiàn)的數(shù)組去重功能,簡單分析了開關(guān)思想的原理,并結(jié)合具體實例形式分析了javascript基于開關(guān)思想實現(xiàn)數(shù)組去重相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
layui實現(xiàn)鼠標(biāo)移動到單元格上顯示數(shù)據(jù)的方法
今天小編就為大家分享一篇layui實現(xiàn)鼠標(biāo)移動到單元格上顯示數(shù)據(jù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
js ondocumentready onmouseover onclick onmouseout 樣式
下面都是一些上面的事件觸發(fā)的事先定義的代碼。2010-07-07
微信小程序methods中定義的方法互相調(diào)用的實例代碼
這篇文章主要介紹了微信小程序methods中定義的方法互相調(diào)用的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-08-08
使用insertAfter()方法在現(xiàn)有元素后添加一個新元素
為javascript添加insertAfter()方法即在現(xiàn)有元素后添加一個新元素,示例如下,大家不妨學(xué)習(xí)下2014-05-05
Ionic實現(xiàn)頁面下拉刷新(ion-refresher)功能代碼
這篇文章主要介紹了使用Ionic實現(xiàn)頁面下拉刷新(ion-refresher)功能的代碼,感興趣的朋友一起看看吧2016-06-06

