單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。前端
若是排隊是由於計算量大,CPU忙不過來,倒也算了,可是不少時候CPU是閒着的,由於IO設備(輸入輸出設備)很慢(好比Ajax操做從網絡讀取數據),不得不等着結果出來,再往下執行。node
JavaScript語言的設計者意識到,這時主線程徹底能夠無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。ajax
因而,全部任務能夠分紅兩種,一種是同步任務(synchronous)
,另外一種是異步任務(asynchronous)
。vim
同步任務
指的是,在主線程上排隊執行的任務
,只有前一個任務執行完畢,才能執行後一個任務;瀏覽器
異步任務
指的是,不進入主線程、而進入"任務隊列"(task queue)的任務
,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。網絡
具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)數據結構
(1)全部同步任務
都在主線程
上執行,造成一個執行棧
(execution context stack)。異步
(2)主線程以外,還存在一個"任務隊列
"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。async
(3)一旦"執行棧"中的全部同步任務
執行完畢,系統就會讀取"任務隊列
",看看裏面有哪些事件。那些對應的異步任務
,因而結束等待狀態,進入執行棧,開始執行。函數
(4)主線程不斷重複上面的第三步。
只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。
舉個例子:
console.log("1"); setTimeout(()=>{ console.log("2"); },0); console.log("3"); // 1 // 3 // 2
運行結果是:一、三、2
setTimeout裏的函數並無當即執行,而是延遲一段時間,符合特定的條件纔開始執行,這就是異步執行操做
。
console.log("1") //是同步任務,放入主線程, setTimeout() //是異步任務,被放入事件列表Event table中,0秒後被推入任務隊列task queue裏, console.log("3") //是同步任務,放入主線程 //當一、3任務先執行完後,主線程去task queue(事件隊列)裏查看是否有可執行的函數,執行setTimeout裏的函數。
"任務隊列"是一個事件的隊列(也能夠理解成消息的隊列),IO設備完成一項任務,就在"任務隊列"中添加一個事件,表示相關的異步任務能夠進入"執行棧"了。主線程讀取"任務隊列",就是讀取裏面有哪些事件。
"任務隊列"中的事件,除了IO設備的事件之外,還包括一些用戶產生的事件(好比鼠標點擊、頁面滾動等等)。只要指定過回調函數,這些事件發生時就會進入"任務隊列",等待主線程讀取。
所謂"回調函數
"(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數
,當主線程開始執行異步任務,就是執行對應的回調函數。
"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。可是,因爲存在後文提到的"定時器"功能,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲
Event Loop
(事件循環)。
堆
(heap)和棧
(stack),【注意,老是要等待棧中的代碼執行完畢後纔會去讀取事件隊列中的事件】
JS中分爲兩種任務類型:宏任務macro task
和微任務micro task
,
在ECMAScript中,micro task稱爲jobs,macro task可稱爲task
1)宏任務(macro task),能夠理解爲每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)
每個task會從頭至尾將這個任務執行完畢,不會執行其它
瀏覽器爲了可以使得JS內部task與DOM任務可以有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行從新渲染
(task->渲染->task->...
)
2)微任務(micro task),能夠理解爲在當前 task 執行結束後當即執行的任務
也就是說,在當前task任務後,下一個task以前,在渲染以前
因此它的響應速度相比setTimeout(setTimeout是task)會更快,由於無需等渲染
也就是說,在某一個macro task執行完後,就會將在它執行期間產生的全部micro task都執行完畢(在渲染前)
1)宏任務(macro task):主代碼塊
,setTimeout
,setInterval
,I/O、UI交互事件、postMessage、MessageChannel、setImmediate(node.js 環境)等(能夠看到,事件隊列中的每個事件都是一個宏任務)
2)微任務(micro task):Promise.then
、MutaionObserver
、MessageChannel
、process.nextTick
(node.js 環境)等
__補充:在node環境下,process.nextTick
的優先級高於Promise
,也就是能夠簡單理解爲:在宏任務結束後會先執行微任務隊列中的nextTickQueue
部分,而後纔會執行微任務中的Promise
部分。
再根據線程來理解下:
宏任務(macro task)中的事件都是放在一個事件隊列中的,而這個隊列由事件觸發線程維護
微任務(micro task)中的全部微任務都是添加到微任務隊列(Job Queues)中,等待當前宏任務執行完畢後執行,而這個隊列由JS引擎線程維護
因此,總結下運行機制
:
舉個例子:
setTimeout(()=>{ console.log("定時器開始執行"); }) new Promise(function(resolve){ console.log("準備執行for循環了"); for(var i=0;i<100;i++){ i==22&&resolve(); } }).then(()=>console.log("執行then函數")); console.log("代碼執行完畢"); //首先執行script下的宏任務,遇到setTimeout,將其放到宏任務的【隊列】裏 //遇到 new Promise直接執行,打印"準備執行for循環" //遇到then方法,是微任務,將其放到微任務的【隊列裏】 //打印 "代碼執行完畢" //本輪宏任務執行完畢,查看本輪的微任務,發現有一個then方法裏的函數, 打印"執行then函數" //到此,本輪的event loop 所有完成。 //下一輪的循環裏,先執行一個宏任務,發現宏任務的【隊列】裏有一個 setTimeout裏的函數,執行打印"定時器開始執行"
因此最後的執行順序就是:【準備執行for循環-->代碼執行完畢-->執行then函數-->定時器開始執行】
若是你以爲這篇文章對你有所幫助,那就順便點個贊吧,點點關注不迷路~
黑芝麻哇,白芝麻發,黑芝麻白芝麻哇發哈!
前端哇發哈