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