JavaScript的單線程和異步詳細(xì)
前言:
說到JavaScript
的單線程(single threaded
)和異步(asynchronous
),很多同學(xué)不禁會(huì)想,這不是自相矛盾么?其實(shí),單線程和異步確實(shí)不能同時(shí)成為一個(gè)語言的特性。js選擇了成為單線程的語言,所以它本身不可能是異步的,但js的宿主環(huán)境(比如瀏覽器,Node
)是多線程的,宿主環(huán)境通過某種方式(事件驅(qū)動(dòng),下文會(huì)講)使得js具備了異步的屬性。往下看,你會(huì)發(fā)現(xiàn)js的機(jī)制是多么的簡(jiǎn)單高效!
瀏覽器:
js是單線程語言,瀏覽器只分配給js一個(gè)主線程,用來執(zhí)行任務(wù)(函數(shù)),但一次只能執(zhí)行一個(gè)任務(wù),這些任務(wù)形成一個(gè)任務(wù)隊(duì)列排隊(duì)等候執(zhí)行,但前端的某些任務(wù)是非常耗時(shí)的,比如網(wǎng)絡(luò)請(qǐng)求,定時(shí)器和事件監(jiān)聽,如果讓他們和別的任務(wù)一樣,都老老實(shí)實(shí)的排隊(duì)等待執(zhí)行的話,執(zhí)行效率會(huì)非常的低,甚至導(dǎo)致頁(yè)面的假死。所以,瀏覽器為這些耗時(shí)任務(wù)開辟了另外的線程,主要包括http請(qǐng)求線程,瀏覽器定時(shí)觸發(fā)器,瀏覽器事件觸發(fā)線程,這些任務(wù)是異步的。下圖說明了瀏覽器的主要線程。
一、任務(wù)隊(duì)列
剛才說到瀏覽器為網(wǎng)絡(luò)請(qǐng)求這樣的異步任務(wù)單獨(dú)開了一個(gè)線程,那么問題來了,這些異步任務(wù)完成后,主線程怎么知道呢?答案就是回調(diào)函數(shù),整個(gè)程序是事件驅(qū)動(dòng)的,每個(gè)事件都會(huì)綁定相應(yīng)的回調(diào)函數(shù),舉個(gè)栗子,有段代碼設(shè)置了一個(gè)定時(shí)器
setTimeout(function(){ console.log(time is out); },50);
執(zhí)行這段代碼的時(shí)候,瀏覽器異步執(zhí)行計(jì)時(shí)操作,當(dāng)50ms到了后,會(huì)觸發(fā)定時(shí)事件,這個(gè)時(shí)候,就會(huì)把回調(diào)函數(shù)放到任務(wù)隊(duì)列里。整個(gè)程序就是通過這樣的一個(gè)個(gè)事件驅(qū)動(dòng)起來的。
所以說,js是一直是單線程的,瀏覽器才是實(shí)現(xiàn)異步的那個(gè)家伙。
說回主線程:
js一直在做一個(gè)工作,就是從任務(wù)隊(duì)列里提取任務(wù),放到主線程里執(zhí)行。下面我們來進(jìn)行更深一步的理解。
我們把剛才了解的概念和圖中做一個(gè)對(duì)應(yīng),上文中說到的瀏覽器為異步任務(wù)單獨(dú)開辟的線程可以統(tǒng)一理解為WebAPIs
,上文中說到的任務(wù)隊(duì)列就是callback queue
,我們所說的主線程就是有虛線組成的那一部分,堆(heap
)和棧(stack
)共同組成了js主線程,函數(shù)的執(zhí)行就是通過進(jìn)棧和出棧實(shí)現(xiàn)的,比如圖中有一個(gè)foo()函數(shù),主線程把它推入棧中,在執(zhí)行函數(shù)體時(shí),發(fā)現(xiàn)還需要執(zhí)行上面的那幾個(gè)函數(shù),所以又把這幾個(gè)函數(shù)推入棧中,等到函數(shù)執(zhí)行完,就讓函數(shù)出棧。等到stack清空時(shí),說明一個(gè)任務(wù)已經(jīng)執(zhí)行完了,這時(shí)就會(huì)從callback queue
中尋找下一個(gè)人任務(wù)推入棧中(這個(gè)尋找的過程,叫做event loop,因?yàn)樗偸茄h(huán)的查找任務(wù)隊(duì)列里是否還有任務(wù))。
二、借以解釋幾個(gè)容易困惑的問題
1、setTimeout(f1,0)是什么鬼
這個(gè)語句最大的疑問是,f1是不是立刻執(zhí)行?答案是不一定,因?yàn)橐粗骶€程內(nèi)的命令是否已經(jīng)執(zhí)行完了,如下代碼:
setTimeout(function(){ console.log(1); },0); console.log(2);
2、Ajax請(qǐng)求是否異步
了解完上文內(nèi)容,我們就知道了,ajax
請(qǐng)求內(nèi)容的時(shí)候是異步的,當(dāng)請(qǐng)求完成后,會(huì)觸發(fā)請(qǐng)求完成的事件,然后把回調(diào)函數(shù)放入callback queue
,等到主線程執(zhí)行該回調(diào)函數(shù)時(shí)還是單線程的。
3、界面渲染線程是單獨(dú)開辟的線程
界面渲染線程是單獨(dú)開辟的線程,是不是DOM一變化,界面就立刻重新渲染?
如果DOM一變化,界面就立刻重新渲染,效率必然很低,所以瀏覽器的機(jī)制規(guī)定界面渲染線程和主線程是互斥的,主線程執(zhí)行任務(wù)時(shí),瀏覽器渲染線程處于掛起狀態(tài)。
三、如何利用瀏覽器的異步機(jī)制
我們已經(jīng)知道,js一直是單線程執(zhí)行的,瀏覽器為幾個(gè)明顯的耗時(shí)任務(wù)單獨(dú)開辟線程解決耗時(shí)問題,但是js除了這幾個(gè)明顯的耗時(shí)問題外,可能我們自己寫的程序里面也會(huì)有耗時(shí)的函數(shù),這種情況怎么處理呢?我們肯定不能自己開辟單獨(dú)的線程,但我們可以利用瀏覽器給我們開放的這幾個(gè)窗口,瀏覽器定時(shí)器線程和事件觸發(fā)線程是好利用的,網(wǎng)絡(luò)請(qǐng)求線程不適合我們使用。下面我們具體看一下:
假設(shè)耗時(shí)函數(shù)是f1,f1是f2的前置任務(wù)。
利用定時(shí)器觸發(fā)線程:
function f1(callback){ setTimeout(function(){ // f1 的代碼 callback(); },0); } f1(f2);
這種寫法的耦合度高。
利用事件觸發(fā)線程:
$f1.on('custom',f2); //這里綁定事件以jQuery寫法為例 function f1(){ setTimeout(function(){ // f1的代碼 $f1.trigger('custom'); },0); }
這種方法通過綁定自定義事件,對(duì)方法一解耦,這樣可以通過綁定不同的事件,實(shí)現(xiàn)不同的回調(diào)函數(shù),但如果應(yīng)用這種方法過多,不利于閱讀程序。
四、異步的好處和適合的場(chǎng)景
異步的好處:
我們直接通過一個(gè)例子對(duì)同步和異步進(jìn)行對(duì)比,假設(shè)有四個(gè)任務(wù)(編號(hào)為1,2,3,4),它們的執(zhí)行時(shí)間都是10ms,其中任務(wù)2是任務(wù)3的前置任務(wù),任務(wù)2需要20ms的響應(yīng)時(shí)間。下面我們做下對(duì)比,你就知道怎么實(shí)現(xiàn)的非阻塞I/O了。
適合的場(chǎng)景:
可以看出,當(dāng)我們的程序需要大量I/O操作和用戶請(qǐng)求時(shí),js這個(gè)具備單線程,異步,事件驅(qū)動(dòng)多種氣質(zhì)的語言是多么應(yīng)景!相比于多線程語言,它不必耗費(fèi)過多的系統(tǒng)開銷,同時(shí)也不必把精力用于處理多線程管理,相比于同步執(zhí)行的語言,宿主環(huán)境的異步和事件驅(qū)動(dòng)機(jī)制又讓它實(shí)現(xiàn)了非阻塞I/O,所以你應(yīng)該知道它適合什么樣的場(chǎng)景了吧!
到此這篇關(guān)于JavaScript
的單線程和異步詳細(xì)的文章就介紹到這了,更多相關(guān)JavaScript
的單線程和異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序 使用picker封裝省市區(qū)三級(jí)聯(lián)動(dòng)實(shí)例代碼
這篇文章主要介紹了微信小程序 使用picker封裝省市區(qū)三級(jí)聯(lián)動(dòng)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10微信小程序request出現(xiàn)400的問題解決辦法
這篇文章主要介紹了微信小程序request出現(xiàn)400的問題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-05-05手機(jī)瀏覽器 后退按鈕強(qiáng)制刷新頁(yè)面方法總結(jié)
這篇文章主要介紹了手機(jī)瀏覽器 后退按鈕強(qiáng)制刷新頁(yè)面方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-10-10Javascript中彈窗confirm與prompt的區(qū)別
今天小編就為大家分享一篇關(guān)于Javascript中彈窗confirm與prompt的區(qū)別,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10JavaScript parseInt0.0000005打印5原理解析
這篇文章主要為大家介紹了JavaScript parseInt0.0000005打印5原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07