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