摘要:換句話說當一個異步過程調用發出后,調用者不會立刻得到結果,而是調用發出后,被調用者通過狀態通知或回調函數處理這個調用。
JavaScript單線程機制
任務隊列JavaScript的一個語言特性(也是這門語言的核心)就是單線程。什么是單線程呢?簡單地說就是同一時間只能做一件事,當有多個任務時,只能按照一個順序一個完成了再執行下一個
為什么JS是單線程的呢?
JS最初被設計用在瀏覽器中,作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM
如果瀏覽器中的JS是多線程的,會帶來很復雜的同步問題
比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?
所以為了避免復雜性,JavaScript從誕生起就是單線程
為了提高CPU的利用率,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以這個標準并沒有改變JavaScript單線程的本質
Event Loop同步和異步
同步和異步關注的是消息通知機制同步:發出調用后,沒有得到結果之前,該調用不返回,一旦調用返回,就得到返回值了。 簡而言之就是調用者主動等待這個調用的結果
異步:調用者在發出調用后這個調用就直接返回了,所以沒有返回結果。換句話說當一個異步過程調用發出后,調用者不會立刻得到結果,而是調用發出后,被調用者通過狀態、通知或回調函數處理這個調用。
阻塞和非阻塞
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回
非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程
單線程意味著同一時間只能進行一件事情,前面的事情結束才能執行后面的事件.當碰到需要時間的IO事件的時候問題就來了,必須等到這些結束后才往下進行,但這時CPU是閑著的.這樣浪費了很多計算機的性能.
JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處于等待中的任務,先運行排在后面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續執行下去.
于是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行。
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行
(4)主線程不斷重復上面的第三步
主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)上圖中,主線程運行的時候,產生堆(heap)和棧(stack),堆中可存放對象, 棧中可存放變量,函數,函數指針,代碼語句等
棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件(click,load,done)
WebAPIs都是多帶帶線程,跟組件中的不一樣,不會阻塞主線程執行,比如獲取后臺數據,若同步就阻塞了,比如HTTP請求又開辟了一個線程當執行棧中的任務完成后,主線程會去讀取事件隊列(先進先出),執行相應的回調函數
舉個例子,查看以下代碼
function read(){ console.log(1); setTimeout(function (){ console.log(2); setTimeout(function (){ console.log(4) }); }); setTimeout(function (){ console.log(5) }) console.log(3); } read();
代碼執行結果:1 3 2 5 4定時器先執行同步代碼打印1,3,setTimeout異步代碼放到事件隊列中,先放的先執行,后放的后執行
"任務隊列"可以放置定時事件,即指定某些代碼在多少時間之后執行定時器功能主要由setTimeout()和setInterval()這兩個函數來完成,它們的內部運行機制完全一樣,區別在于前者指定的代碼是一次性執行,后者則為反復執行,主要以setTimeout舉例說明
setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數
setTimeout(function () { console.log(3) }, 2000); setTimeout(function () { console.log(1); setTimeout(function () { console.log(2); }, 1000); }, 1000);
Promise與process.nextTick(callback)執行結果是:1 3 2
setTimeout()將事件放到等待任務隊里中,當主任務隊列的任務執行完后,再執行等待任務隊列,等待任務隊里中先返回的先執行
setTimeout()有時候明明寫的延時3秒,實際卻5,6秒才執行函數,這是怎么回事呢?
setTimeout()只是將事件插入了“任務隊列”,必須等到當前代碼(執行棧)執行完,主線程才會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以并沒有辦法保證回調函數一定會在setTimeout()指定的時間執行
除了廣義的同步任務和異步任務,我們對任務有更精細的定義:
macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
micro-task(微任務):Promise,process.nextTick
- process.nextTick:在事件循環的下一次循環中調用 callback 回調函數。效果是將一個函數推遲到代碼書寫的下一個同步方法執行完畢時或異步方法的事件回調函數開始執行時;與setTimeout(fn, 0) 函數的功能類似,但它的效率高多了
不同類型的任務會進入對應的Event Queue,比如 setTimeout 和 setInterval 會進入相同的Event Queue
事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)后,開始第一次循環。接著執行所有的微任務。然后再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。
事件循環,宏任務,微任務的關系如下所示:
宏任務=>執行結束=>有可執行的微任務=>執行所有微任務=>開始新的宏任務
宏任務=>執行結束=>沒有可執行的微任務=>開始新的宏任務
我們用一段代碼說明:
setTimeout(function () { console.log("setTimeout"); }); new Promise(function (resolve) { console.log("promise"); }).then(function () { console.log("then"); }); console.log("console");
這段代碼作為宏任務,進入主線程
先遇到 setTimeout ,那么將其回調函數注冊后分發到宏任務Event Queue
接下來遇到了 Promise , new Promise 立即執行, then 函數分發到微任務Event Queue
遇到 console.log() ,立即執行
-整體代碼script作為第一個宏任務執行結束,看看有哪些微任務?我們發現了 then 在微任務Event Queue里面執行
第一輪事件循環結束了,我們開始第二輪循環,當然要從宏任務Event Queue開始。我們發現了宏任務Event Queue中 setTimeout 對應的回調函數,立即執行
結束
我們再看下一段代碼說明:
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log("TIMEOUT FIRED"); }, 0)
以上代碼執行結果:1 2 TIMEOUT FIRED上面代碼中,由于process.nextTick方法指定的回調函數,總是在當前"執行棧"的尾部觸發,所以不僅函數A比setTimeout指定的回調函數timeout先執行,而且函數B也比timeout先執行。這說明,如果有多個process.nextTick語句(不管它們是否嵌套),將全部在當前"執行棧"執行
我們再看下一段代碼說明:
function a() { setTimeout(function () { console.log("a2"); }, 0); process.nextTick(function () { console.log("a1") }); } function b() { process.nextTick(function () { console.log("b1"); }) } a(); b();
一個函數執行會形成一個執行棧,任務隊列里的回調函數每次只取一個,它執行的時候會形成一個執行棧,當你第一次運行這個腳本的時候,這個腳本的里所有的同步代碼都會在一個執行棧里
a的執行和b的執行在一個執行棧里,它們共同在第一個宏任務中
a執行時候,會把a2放入宏任務隊列,把a1放入微任務隊列。
b執行的時候,把b1放入微任務隊列
-------------------第一個宏任務執行完畢-------------------------
宏任務執行完畢后會把微任務隊列清空,也就是把a1 和b1都執行,輸出a1和b1
-------------------第一個微任務隊列清空--------------------------
然后從宏任務隊列中取出下一個宏任務,也就是a2執行.輸出a2
為什么一個宏任務要搭配處理一個微任務
因為這樣最合理,微任務就是在有空時需要立即執行的任務,宏任務相比微任務可以滯后執行。他們雖然都屬于異步任務,但是通過這種優先級的設置達到了控制異步回調執行順序的目的。值得注意的是:同步代碼執行完會先清空微任務,然后取出宏任務隊列里的第一個事件對應的回調到執行棧執行,然后再清空一次微任務,如此循環...通過以上三段代碼,您是否對JS的執行順序有所了解呢
我們來分析一段較復雜的代碼,看看你是否真的掌握了js的執行機制
console.log("main1"); setTimeout(function () { console.log("setTimeout"); process.nextTick(function () { console.log("process.nextTick2"); }); }, 0); new Promise(function (resolve, reject) { console.log("promise"); resolve(); }).then(function () { console.log("promise then"); }); process.nextTick(function () { console.log("process.nextTick1"); }); console.log("main2");
以上代碼的執行結果是:main1=>promise=>main2=>process.nextTick1=>promise then=>setTimeout=>process.nextTick2
系統啟動執行腳本,這個腳本就是一個宏任務,執行代碼塊中所有的同步代碼,輸出main1
next1放入微任務,setTimeout+nextTick2(下一輪)放入宏任務隊列
promise構造函數部分是同步的,立刻執行輸出promise,promise then放入微任務
下面同步代碼輸出main2
接下來執行微任務輸出nextTick1,promise then
接下來執行宏任務輸出setTimeout,將nexttick2放入微任務隊列
接下來執行微任務nexttick2
nextTick是由node自己定義并實現的概念,它的回調調用入口在event loop過程中MakeCallback函數的末尾,驅動調用清空js層的queue,最后再執行microtasks,適當處理下可能觸發的promise,明顯 process.nextTick1> promise.then
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92624.html
摘要:主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。上面也提到,在到達指定時間時,定時器就會將相應回調函數插入任務隊列尾部。這就是定時器功能。關于定時器的重要補充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。上面也提到,在到達指定時間時,定時器就會將相應回調函數插入任務隊列尾部。這就是定時器功能。關于定時器的重要補充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。上面也提到,在到達指定時間時,定時器就會將相應回調函數插入任務隊列尾部。這就是定時器功能。關于定時器的重要補充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...
摘要:的單線程,與它的用途有關。只要指定過回調函數,這些事件發生時就會進入任務隊列,等待主線程讀取。四主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。令人困惑的是,文檔中稱,指定的回調函數,總是排在前面。 原文:http://www.cnblogs.com/Master... 一、為什么JavaScript是單線程? JavaScript語言的一大特點...
摘要:圖片轉引自的演講和兩個定時器中回調的執行邏輯便是典型的機制。異步編程關于異步編程我的理解是,在執行環境所提供的異步機制之上,在應用編碼層面上實現整體流程控制的異步風格。 問題背景 在一次開發任務中,需要實現如下一個餅狀圖動畫,基于canvas進行繪圖,但由于對于JS運行環境中異步機制的不了解,所以遇到了一個棘手的問題,始終無法解決,之后在與同事交流之后才恍然大悟。問題的根節在于經典的J...
閱讀 3615·2021-11-24 10:25
閱讀 2534·2021-11-24 09:38
閱讀 1225·2021-09-08 10:41
閱讀 2911·2021-09-01 10:42
閱讀 2581·2021-07-25 21:37
閱讀 1987·2019-08-30 15:56
閱讀 920·2019-08-30 15:55
閱讀 2756·2019-08-30 15:54