上星期面試被問到了事件執行順序的問題,想起來以前看《深刻淺出Node.js》
時看到這一章就忽略了,此次來分析一下JavaScript的事件執行順序。廢話少說,正題開始。javascript
首先咱們要知道JavaScript
是一門單線程解釋型語言。這就意味着在同一個時間下,咱們只能執行一條命令。之因此它是一門單線程語言,和它的用途有關。
JavaScript設計出來的初衷是爲了加強瀏覽器與用戶的交互,尤爲是表單的交互,而以後的Ajax技術也是爲了使表單的交互更加人性化而發明出來的。由於JavaScript是一門解釋型的語言,而解釋器內嵌於瀏覽器,這個解釋器是單線程的。
之因此不設計成多線程是由於渲染網頁的時候多線程容易引發死鎖或者資源衝突等問題。可是瀏覽器自己是多線程的,好比解釋運行JavaScript的同時還在加載網絡資源。html
Why doesn't JavaScript support multithreading?前端
<!-- more -->html5
單線程就意味着若是你要運行不少命令,那麼這些命令須要排序,通常狀況下,這些命令是從上到下排序執行(由於解釋器是從文件頂部開始)。好比如下代碼是按照順序執行的。java
console.log("1"); console.log("2"); console.log("3"); //1 //2 //3
可是咱們還有知道在JavaScript裏有異步編程的說法,好比Ajax,setTimeout,setInterval或者ES6中的Promise,async,await。面試
一條命令的執行在計算機裏的意思就是它此時在使用CPU等資源,那麼由於想要得到CPU資源的命令有不少,而CPU執行命令也須要時間去運算得到結果,因而就有了同步異步的概念。ajax
同步就是在發出一個CPU請求時,在沒有獲得結果以前,該CPU請求就不返回。可是一旦調用返回,就獲得返回值了。編程
異步表示CPU請求在發出以後,這個調用就直接返回了,因此沒有返回結果。在運行結束後,須要經過一系列手段來得到返回值promise
這時候就要引入進程和線程的概念。瀏覽器
概念:進程是一個具備必定獨立功能的程序在一個數據集上的一次動態執行的過程,是操做系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體。
因爲進程對於CPU的使用是輪流的,那麼就存在進程的切換,可是因爲如今的程序都比較大,切換的開銷很大會浪費CPU的資源,因而就發明了線程,把一個大的進程分解成多個線程共同執行。
假如我是鳴人,我想吃不少拉麪,若是我一我的吃10碗的話,那我就是一個進程一個線程完成吃拉麪這件事情。
可是若是我用9個分身和我一塊兒吃10碗拉麪,那我就是一個進程用9個線程去完成吃拉麪這件事情。
而多進程這表示名人在一樂拉麪裏面吃拉麪的同時,好色仙人在偷看妹子洗澡~ ~。好色仙人是單進程單線程去偷看的哦!
瀏覽器的內核是多線程的,在內核控制下各線程相互配合以保持同步,一個瀏覽器一般由一下線程組成:
這些線程的做用:
由於JavaScript是單線程的,而瀏覽器是多線程的,因此爲了執行不一樣的同步異步的代碼,JavaScript運行的環境採用裏事件循環和消息隊列來達到目的。
每一個線程的任務執行順序都是FIFO(先進先出)
在JavaScript運行的環境中,有一個負責程序自己的運行,做爲主線程;另外一個負責主線程與其餘線程的通訊,被稱爲Event Loop 線程
。
每當主線程遇到異步的任務,把他們移入到Event Loop 線程
,而後主線程繼續運行,等到主線程徹底運行完以後,再去Event Loop 線程
拿結果。
而每一個異步任務都包含着與它相關聯的信息,好比運行狀態,回調函數等。
由此咱們能夠知道,同步任務和異步任務會被分發到不一樣的線程去執行。
如今咱們就能夠分析一下一下代碼的運行結果了。
setTimeout(()=>{console.log("我纔是第一");},0); console.log("我是第一");
console.log("我是第一");
console.log("我纔是第一");
;這裏值得一提的是,setTimeout(callback,0)
指的是主線程中的同步任務運行完了以後馬上由Event Loop 線程調入主線程。
而計時是在調入Event Loop線程註冊時開始的,此時setTimeout的回調函數執行時間
與主線程運行結束的時間相關。
關於setTimeout要補充的是,即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。
須要注意的是,此函數是每隔一段時間將回調函數放入Event Loop線程。
一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那麼就徹底看不出來有時間間隔了
micro-task(微任務)
與 macro-task(宏任務)
Event Loop線程
中包含任務隊列(用來對不一樣優先級的異步事件進行排序),而任務隊列又分爲macro-task(宏任務)
與micro-task(微任務)
,在最新標準中,它們被分別稱爲task
與jobs
。
任務源
。而進入任務隊列的是他們指定的具體執行任務(回調函數)。來自不一樣的任務源的任務會進入到不一樣的任務隊列中,而不一樣的任務隊列執行過程以下:
執行過程以下:
JavaScript引擎首先從macro-task
中取出第一個任務,
執行完畢後,將micro-task
中的全部任務取出,按順序所有執行;
而後再從macro-task
中取下一個,
執行完畢後,再次將micro-task
中的所有取出;
循環往復,直到兩個隊列中的任務都取完。
console.log("start"); var promise = new Promise((resolve) => { console.log("promise start.."); resolve("promise"); }); //3 promise.then((val) => console.log(val)); setTimeout(()=>{console.log("setTime1")},0); console.log("test end...")
這裏咱們按順序來分析。
console.log("start");
。Promises
直接運行console.log("promise start..")
。promise.then
,存入到micro-task隊列
中。setTimeout
,存入到macro-task隊列
中。console.log("test end...")
;promise.then
,輸出promise
macro-task隊列
中的setTimeout
,運行console.log("setTime1");
輸出的順序就是
// start // promise start // test end... // promise //setTime1
async function testSometing() { console.log("執行testSometing"); return "testSometing"; } async function testAsync() { console.log("執行testAsync"); return Promise.resolve("hello async"); } async function test() { console.log("test start..."); const v1 = await testSometing(); console.log(v1); const v2 = await testAsync(); console.log(v2); console.log(v1, v2); } test(); var promise = new Promise((resolve) => { console.log("promise start.."); resolve("promise"); }); //3 promise.then((val) => console.log(val)); setTimeout(()=>{console.log("setTime1")},3000); console.log("test end...")