摘要:每個線程的任務執行順序都是先進先出在運行的環境中,有一個負責程序本身的運行,作為主線程另一個負責主線程與其他線程的通信,被稱為線程。主線程繼續執行我是第一主線程執行完畢,從線程讀取回調函數。
前言
上星期面試被問到了事件執行順序的問題,想起來之前看《深入淺出Node.js》時看到這一章就忽略了,這次來分析一下JavaScript的事件執行順序。廢話少說,正題開始。
單線程JavaScript首先我們要知道JavaScript是一門單線程解釋型語言。這就意味著在同一個時間下,我們只能執行一條命令。之所以它是一門單線程語言,和它的用途有關。
JavaScript設計出來的初衷是為了增強瀏覽器與用戶的交互,尤其是表單的交互,而之后的Ajax技術也是為了使表單的交互更加人性化而發明出來的。因為JavaScript是一門解釋型的語言,而解釋器內嵌于瀏覽器,這個解釋器是單線程的。
之所以不設計成多線程是因為渲染網頁的時候多線程容易引起死鎖或者資源沖突等問題。但是瀏覽器本身是多線程的,比如解釋運行JavaScript的同時還在加載網絡資源。
Why doesn"t JavaScript support multithreading?
事件循環單線程就意味著如果你要運行很多命令,那么這些命令需要排序,一般情況下,這些命令是從上到下排序執行(因為解釋器是從文件頂部開始)。比如以下代碼是按照順序執行的。
console.log("1"); console.log("2"); console.log("3"); //1 //2 //3
但是我們還有知道在JavaScript里有異步編程的說法,比如Ajax,setTimeout,setInterval或者ES6中的Promise,async,await。
那么什么是同步和異步呢?一條命令的執行在計算機里的意思就是它此時在使用CPU等資源,那么因為想要獲得CPU資源的命令有很多,而CPU執行命令也需要時間去運算獲得結果,于是就有了同步異步的概念。
同步就是在發出一個CPU請求時,在沒有得到結果之前,該CPU請求就不返回。但是一旦調用返回,就得到返回值了。
異步表示CPU請求在發出之后,這個調用就直接返回了,所以沒有返回結果。在運行結束后,需要通過一系列手段來獲得返回值
這時候就要引入進程和線程的概念。
進程與線程 進程概念:進程是一個具有一定獨立功能的程序在一個數據集上的一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體。
線程由于進程對于CPU的使用是輪流的,那么就存在進程的切換,但是由于現在的程序都比較大,切換的開銷很大會浪費CPU的資源,于是就發明了線程,把一個大的進程分解成多個線程共同執行。
區別進程是操作系統分配資源的最小單位,線程是程序執行的最小單位。
一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執行路線;
進程之間相互獨立,但同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)及一些進程級的資源(如打開文件和信號)。
調度和切換:線程上下文切換比進程上下文切換要快得多。
舉個例子假如我是鳴人,我想吃很多拉面,如果我一個人吃10碗的話,那我就是一個進程一個線程完成吃拉面這件事情。
但是如果我用9個分身和我一起吃10碗拉面,那我就是一個進程用9個線程去完成吃拉面這件事情。
而多進程這表示名人在一樂拉面里面吃拉面的同時,好色仙人在偷看妹子洗澡~ ~。好色仙人是單進程單線程去偷看的哦!
瀏覽器的內核是多線程的,在內核控制下各線程相互配合以保持同步,一個瀏覽器通常由一下線程組成:
GUI 渲染線程
JavaScript引擎線程
事件觸發線程
異步http請求線程
EventLoop輪詢的處理線程
這些線程的作用:
UI線程用于渲染頁面
js線程用于執行js任務
瀏覽器事件觸發線程用于控制交互,響應用戶
http線程用于處理請求,ajax是委托給瀏覽器新開一個http線程
EventLoop處理線程用于輪詢消息隊列
JavaScript事件循環和消息隊列(瀏覽器環境)因為JavaScript是單線程的,而瀏覽器是多線程的,所以為了執行不同的同步異步的代碼,JavaScript運行的環境采用里事件循環和消息隊列來達到目的。
每個線程的任務執行順序都是FIFO(先進先出)
在JavaScript運行的環境中,有一個負責程序本身的運行,作為主線程;另一個負責主線程與其他線程的通信,被稱為Event Loop 線程。
每當主線程遇到異步的任務,把他們移入到Event Loop 線程,然后主線程繼續運行,等到主線程完全運行完之后,再去Event Loop 線程拿結果。
而每個異步任務都包含著與它相關聯的信息,比如運行狀態,回調函數等。
由此我們可以知道,同步任務和異步任務會被分發到不同的線程去執行。
現在我們就可以分析一下一下代碼的運行結果了。
setTimeout(()=>{console.log("我才是第一");},0); console.log("我是第一");
因為setTimeout是異步的事件,所以主線程把它調入Event Loop線程進行注冊。
主線程繼續執行console.log("我是第一");
主線程執行完畢,從Event Loop 線程讀取回調函數。再執行console.log("我才是第一");;
setTimeout 和 setInterval setTimeout這里值得一提的是,setTimeout(callback,0)指的是主線程中的同步任務運行完了之后立刻由Event Loop 線程調入主線程。
而計時是在調入Event Loop線程注冊時開始的,此時setTimeout的回調函數執行時間與主線程運行結束的時間相關。
關于setTimeout要補充的是,即便主線程為空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。
需要注意的是,此函數是每隔一段時間將回調函數放入Event Loop線程。
一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那么就完全看不出來有時間間隔了
Event Loop線程中包含任務隊列(用來對不同優先級的異步事件進行排序),而任務隊列又分為macro-task(宏任務)與micro-task(微任務),在最新標準中,它們被分別稱為task與jobs。
macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)
setTimeout/Promise等我們稱之為任務源。而進入任務隊列的是他們指定的具體執行任務(回調函數)。
來自不同的任務源的任務會進入到不同的任務隊列中,而不同的任務隊列執行過程如下:
執行過程如下:
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...")
這里我們按順序來分析。
整體script代碼作為一個宏任務進入主線程,運行console.log("start");。
然后遇到Promises直接運行console.log("promise start..")。
然后遇到promise.then,存入到micro-task隊列中。
然后遇到setTimeout,存入到macro-task隊列中。
于然后運行console.log("test end...");
在這一輪中,宏任務運行結束,運行micro-task隊列中的 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...")感謝以下文章
前端基礎進階(十二):深入核心,詳解事件循環機制
[[JavaScript] Macrotask Queue和Microtask Queue](http://www.jianshu.com/p/3ed9...
JavaScript運行機制(堆、棧、消息隊列)
JavaScript 運行機制詳解:再談Event Loop
這一次,徹底弄懂 JavaScript 執行機制
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90077.html
摘要:瀏覽器在解析文檔流的時候,如果遇到一個標簽,則會等到這個代碼塊都加載完之后再對代碼進行預編譯,然后在執行。執行完畢后,瀏覽器會繼續解析西門的文檔流,同時也準備好處理下一個代碼塊。同時,也避開了文檔流對執行的限制。 本文章記錄本人在學習 JavaScript 中看書理解到的一些東西,加深記憶和并且整理記錄下來,方便之后的復習。 在 html 文檔中的執行順序 js代碼執行順序...
摘要:先說下這個老話題連續賦值例結果是什么這句簡單,而這句呢答案是,變成了全局變量了這是實際執行順序未使用聲明,所以變全局變量了例很早以前的面試題目了,相信很多人知道答案,考點詞法分析執行順序運算符優先級等這是我理解的實際執行順序我是這么猜想的自 先說下這個老話題:連續賦值 例1: function a(){ var o1 = o2 = 5; } a(); console.l...
摘要:一概念是一個單線程解釋型的編程語言。預編譯大致可分為步創建對象找形參和變量聲明,將形參和變量名作為屬性名,值為將實參值和形參統一在函數體里面找函數聲明,值賦予函數體。 一、JavaScript概念 JavaScript ( JS ) 是一個單線程、解釋型的編程語言。 二、JavaScript語言特點 2.1 單線程 JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能...
摘要:如果對語法分析和預編譯,還有疑問引擎執行的過程的理解語法分析和預編譯階段。參與執行過程的線程分別是引擎線程也稱為內核,負責解析執行腳本程序的主線程例如引擎。以上便是引擎執行宏任務的整個過程。 一、概述 js引擎執行過程主要分為三個階段,分別是語法分析,預編譯和執行階段,上篇文章我們介紹了語法分析和預編譯階段,那么我們先做個簡單概括,如下: 1、語法分析: 分別對加載完成的代碼塊進行語法...
摘要:如果對語法分析和預編譯,還有疑問引擎執行的過程的理解語法分析和預編譯階段。參與執行過程的線程分別是引擎線程也稱為內核,負責解析執行腳本程序的主線程例如引擎。以上便是引擎執行宏任務的整個過程。一、概述 js引擎執行過程主要分為三個階段,分別是語法分析,預編譯和執行階段,上篇文章我們介紹了語法分析和預編譯階段,那么我們先做個簡單概括,如下: 1、語法分析: 分別對加載完成的代碼塊進行語法檢驗,語...
閱讀 3588·2021-09-13 10:28
閱讀 1936·2021-08-10 09:43
閱讀 1009·2019-08-30 15:44
閱讀 3177·2019-08-30 13:14
閱讀 1829·2019-08-29 16:56
閱讀 2937·2019-08-29 16:35
閱讀 2842·2019-08-29 12:58
閱讀 863·2019-08-26 13:46