????????都知道JavaScript是單線程語言,而單線程的設定其實與它的用途有關缭黔。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動蒂破,以及操作DOM馏谨。這決定了它只能是單線程,否則會帶來很復雜的同步問題附迷。比如惧互,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內容喇伯,另一個線程刪除了這個節(jié)點喊儡,這時瀏覽器應該以哪個線程為準?
????????所以稻据,為了避免復雜性艾猜,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征匆赃,將來也不會改變淤毛。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準算柳,允許JavaScript腳本創(chuàng)建多個線程低淡,但是子線程完全受主線程控制,且不得操作DOM瞬项。所以蔗蹋,這個新標準并沒有改變JavaScript單線程的本質。
其實同步和異步囱淋,無論如何猪杭,做事情的時候都是只有一條流水線(單線程),同步和異步的差別就在于這條流水線上各個流程的執(zhí)行順序不同绎橘。
????????最基礎的異步是setTimeout和setInterval函數(shù)胁孙,很常見,但是很少人有人知道其實這就是異步称鳞,盡管我們設置了setTimeout(function涮较,time)中的等待時間為0,結果其中的function還是后執(zhí)行冈止。
? ??<script type="text/javascript">
? ? ? ? console.log( "1" );?
? ? ? ? setTimeout(function() {?
? ? ? ? ? ? console.log( "2" )?
? ? ? ? }, 0 );?
? ? ? ? setTimeout(function() {?
? ? ? ? ? ? console.log( "3" )?
? ? ? ? }, 0 );?
? ? ? ? setTimeout(function() {?
? ? ? ? ? ? console.log( "4" )?
? ? ? ? }, 0 );?
? ? ? ? console.log( "5" );?
</script>?
火狐瀏覽器的api文檔有這樣一句話:Because even though setTimeout was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity, not immediately. Currently executing code must complete before functions on the queue are executed, the resulting execution order may not be as expected.
意思就是:盡管setTimeout的time延遲時間為0狂票,其中的function也會被放入一個隊列中,等待下一個機會執(zhí)行熙暴,當前的代碼(指不需要加入隊列中的程序)必須在該隊列的程序完成之前完成闺属,因此結果可能不與預期結果相同。
這里說到了一個“隊列”(即任務隊列)周霉,該隊列放的是什么呢掂器,放的就是setTimeout中的function,這些function依次加入該隊列俱箱,即該隊列中所有function中的程序將會在該隊列以外的所有代碼執(zhí)行完畢之后再以此執(zhí)行国瓮,這是為什么呢?因為在執(zhí)行程序的時候狞谱,瀏覽器會默認setTimeout以及ajax請求這一類的方法都是耗時程序(盡管可能不耗時)乃摹,將其加入一個隊列中,該隊列是一個存儲耗時程序的隊列跟衅,在所有不耗時程序執(zhí)行過后孵睬,再來依次執(zhí)行該隊列中的程序。
又回到了最初的起點——javascript是單線程伶跷。單線程就意味著掰读,所有任務需要排隊秘狞,前一個任務結束,才會執(zhí)行后一個任務蹈集。如果前一個任務耗時很長谒撼,后一個任務就不得不一直等著。于是就有一個概念——任務隊列雾狈。如果排隊是因為計算量大,CPU忙不過來抵皱,倒也算了善榛,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網絡讀取數(shù)據(jù))呻畸,不得不等著結果出來移盆,再往下執(zhí)行。于是JavaScript語言的設計者意識到伤为,這時主線程完全可以不管IO設備咒循,掛起處于等待中的任務,先運行排在后面的任務绞愚。等到IO設備返回了結果叙甸,再回過頭,把掛起的任務繼續(xù)執(zhí)行下去位衩。
于是裆蒸,所有任務可以分成兩種,一種是同步任務(synchronous)糖驴,另一種是異步任務(asynchronous)僚祷。同步任務指的是,在主線程上排隊執(zhí)行的任務贮缕,只有前一個任務執(zhí)行完畢辙谜,才能執(zhí)行后一個任務;異步任務指的是感昼,不進入主線程装哆、而進入"任務隊列"(task queue)的任務,只有等主線程任務執(zhí)行完畢抑诸,"任務隊列"開始通知主線程烂琴,請求執(zhí)行任務,該任務才會進入主線程執(zhí)行蜕乡。
具體來說奸绷,異步運行機制如下:
(1)所有同步任務都在主線程上執(zhí)行层玲,形成一個執(zhí)行棧(execution context stack)号醉。
》粗ⅰ(2)主線程之外,還存在一個"任務隊列"(task queue)畔派。只要異步任務有了運行結果铅碍,就在"任務隊列"之中放置一個事件。
∠咭(3)一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢胞谈,系統(tǒng)就會讀取"任務隊列",看看里面有哪些事件憨愉。那些對應的異步任務烦绳,于是結束等待狀態(tài),進入執(zhí)行棧配紫,開始執(zhí)行径密。
(4)主線程不斷重復上面的第三步躺孝。
只要主線程空了享扔,就會去讀取"任務隊列",這就是JavaScript的運行機制植袍。這個過程會不斷重復惧眠。
"任務隊列"是一個事件的隊列(也可以理解成消息的隊列),IO設備完成一項任務奋单,就在"任務隊列"中添加一個事件锉试,表示相關的異步任務可以進入"執(zhí)行棧"了。主線程讀取"任務隊列"览濒,就是讀取里面有哪些事件呆盖。
"任務隊列"中的事件,除了IO設備的事件以外贷笛,還包括一些用戶產生的事件(比如鼠標點擊应又、頁面滾動等等),比如$(selectot).click(function)乏苦,這些都是相對耗時的操作株扛。只要指定過這些事件的回調函數(shù),這些事件發(fā)生時就會進入"任務隊列"汇荐,等待主線程讀取洞就。
所謂"回調函數(shù)"(callback),就是那些會被主線程掛起來的代碼掀淘,前面說的點擊事件$(selectot).click(function)中的function就是一個回調函數(shù)旬蟋。異步任務必須指定回調函數(shù),當主線程開始執(zhí)行異步任務革娄,就是執(zhí)行對應的回調函數(shù)倾贰。例如ajax的success冕碟,complete,error也都指定了各自的回調函數(shù)匆浙,這些函數(shù)就會加入“任務隊列”中安寺,等待執(zhí)行。