摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執(zhí)行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。執(zhí)行異步操作事件完成,回調函數(shù)進入。主線程從讀取回調函數(shù)并執(zhí)行。
最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址了解進程和線程
進程是應用程序的執(zhí)行實例,每一個進程都是由私有的虛擬地址空間、代碼、數(shù)據(jù)和其它系統(tǒng)資源所組成;進程在運行過程中能夠申請創(chuàng)建和使用系統(tǒng)資源(如- 獨立的內存區(qū)域等),這些資源也會隨著進程的終止而被銷毀。
而線程則是進程內的一個獨立執(zhí)行單元,在不同的線程之間是可以共享進程資源的,所以在多線程的情況下,需要特別注意對臨界資源的訪問控制。
在系統(tǒng)創(chuàng)建進程之后就開始啟動執(zhí)行進程的主線程,而進程的生命周期和這個主線程的生命周期一致,主線程的退出也就意味著進程的終止和銷毀。
主線程是由系統(tǒng)進程所創(chuàng)建的,同時用戶也可以自主創(chuàng)建其它線程,這一系列的線程都會并發(fā)地運行于同一個進程中。
瀏覽器是多進程的詳情看我上篇總結瀏覽器執(zhí)行機制的文章-深入前端-徹底搞懂瀏覽器運行機制
瀏覽器每打開一個標簽頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。
Browser進程:瀏覽器的主進程(負責協(xié)調、主控),只有一個。作用有
第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創(chuàng)建
GPU進程:最多一個,用于3D繪制等
瀏覽器渲染進程(瀏覽器內核)
javascript是一門單線程語言jS運行在瀏覽器中,是單線程的,但每個tab標簽頁都是一個進程,都含有不同JS線程分別執(zhí)行,,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
既然是單線程的,在某個特定的時刻只有特定的代碼能夠被執(zhí)行,并阻塞其它的代碼。而瀏覽器是事件驅動的(Event driven),瀏覽器中很多行為是異步(Asynchronized)的,會創(chuàng)建事件并放入執(zhí)行隊列中。javascript引擎是單線程處理它的任務隊列,你可以理解成就是普通函數(shù)和回調函數(shù)構成的隊列。當異步事件發(fā)生時,如(鼠標點擊事件發(fā)生、定時器觸發(fā)事件發(fā)生、XMLHttpRequest完成回調觸發(fā)等),將他們放入執(zhí)行隊列,等待當前代碼執(zhí)行完成。
javascript引擎是基于事件驅動單線程執(zhí)行的,JS引擎一直等待著任務隊列中任務的到來,然后加以處理,瀏覽器無論什么時候都只有一個JS線程在運行JS程序。所以一切javascript版的"多線程"都是用單線程模擬出來的
為什么JavaScript是單線程?與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準?
任務隊列"任務隊列"是一個事件的隊列(也可以理解成消息的隊列),IO設備完成一項任務,就在"任務隊列"中添加一個事件,表示相關的異步任務可以進入"執(zhí)行棧"了。主線程讀取"任務隊列",就是讀取里面有哪些事件。
"任務隊列"中的事件,除了IO設備的事件以外,還包括一些用戶產(chǎn)生的事件(比如鼠標點擊、頁面滾動等等),ajax請求等。只要指定過回調函數(shù),這些事件發(fā)生時就會進入"任務隊列",等待主線程讀取。
所謂"回調函數(shù)"(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數(shù),當主線程開始執(zhí)行異步任務,就是執(zhí)行對應的回調函數(shù)。
"任務隊列"是一個先進先出的數(shù)據(jù)結構,排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過程基本上是自動的,只要執(zhí)行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。但是,由于存在后文提到的"定時器"功能,主線程首先要檢查一下執(zhí)行時間,某些事件只有到了規(guī)定的時間,才能返回主線程。
同步和異步任務既然js是單線程,那么問題來了,某一些非常耗時間的任務就會導致阻塞,難道必須等前面的任務一步一步執(zhí)行玩嗎?
比如我再排隊就餐,前面很長的隊列,我一直在那里等豈不是很傻逼,說以就會有排號系統(tǒng)產(chǎn)生,我們訂餐后給我們一個號碼,叫到號碼直接去就行了,沒交我們之前我們可以去干其他的事情。
因此聰明的程序員將任務分為兩類:
同步任務:同步任務指的是,在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務;
異步任務:異步任務指的是,不進入主線程、而進入"任務隊列"(Event queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執(zhí)行了,該任務才會進入主線程執(zhí)行。
任務有更精細的定義:macro-task(宏任務):包括整體代碼script(同步宏任務),setTimeout、setInterval(異步宏任務)
micro-task(微任務):Promise,process.nextTick,ajax請求(異步微任務)
macrotask(又稱之為宏任務)可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調并放到執(zhí)行棧中執(zhí)行)
每一個task會從頭到尾將這個任務執(zhí)行完畢,不會執(zhí)行其它
瀏覽器為了能夠使得JS內部task與DOM任務能夠有序的執(zhí)行,會在一個task執(zhí)行結束后,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染
(task->渲染->task->...)
也就是說,在當前task任務后,下一個task之前,在渲染之前
所以它的響應速度相比setTimeout(setTimeout是task)會更快,因為無需等渲染
也就是說,在某一個macrotask執(zhí)行完后,就會將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)
主線程運行的時候,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件(click,load,done)。只要棧中的代碼執(zhí)行完畢,主線程就會去讀取"任務隊列",依次執(zhí)行那些事件所對應的回調函數(shù)。
那怎么知道主線程執(zhí)行棧為執(zhí)行完畢?js引擎存在monitoring process進程,會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數(shù)。
第一輪事件循環(huán):
主線程執(zhí)行js整段代碼(宏任務),將ajax、setTimeout、promise等回調函數(shù)注冊到Event Queue,并區(qū)分宏任務和微任務。
主線程提取并執(zhí)行Event Queue 中的ajax、promise等所有微任務,并注冊微任務中的異步任務到Event Queue。
第二輪事件循環(huán):
主線程提取Event Queue 中的第一個宏任務(通常是setTimeout)。
主線程執(zhí)行setTimeout宏任務,并注冊setTimeout代碼中的異步任務到Event Queue(如果有)。
執(zhí)行Event Queue中的所有微任務,并注冊微任務中的異步任務到Event Queue(如果有)。
類似的循環(huán):宏任務每執(zhí)行完一個,就清空一次事件隊列中的微任務。
注意:事件隊列中分“宏任務隊列”和“微任務隊列”,每執(zhí)行一次任務都可能注冊新的宏任務或微任務到相應的任務隊列中,只要遵循“每執(zhí)行一個宏任務,就會清空一次事件隊列中的所有微任務”這一循環(huán)規(guī)則,就不會弄亂。
說了那么多來點實例吧 ajax普通異步請求實例let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log("發(fā)送成功!"); } }) console.log("代碼執(zhí)行結束");
1.執(zhí)行整個代碼,遇到ajax異步操作
2.ajax進入Event Table,注冊回調函數(shù)success。
3.執(zhí)行console.log("代碼執(zhí)行結束")。
4.執(zhí)行ajax異步操作
5.ajax事件完成,回調函數(shù)success進入Event Queue。
5.主線程從Event Queue讀取回調函數(shù)success并執(zhí)行。
setTimeout(function(){ console.log("定時器開始啦") }); new Promise(function(resolve){ console.log("馬上執(zhí)行for循環(huán)啦"); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); } }).then(function(){ console.log("執(zhí)行then函數(shù)啦") }); console.log("代碼執(zhí)行結束");
1.整段代碼作為宏任務執(zhí)行,遇到setTimeout宏任務分配到宏任務Event Queue中
2.遇到promise內部為同步方法直接執(zhí)行-“馬上執(zhí)行for循環(huán)啦”
3.注冊then回調到Eventqueen
4.主代碼宏任務執(zhí)行完畢-“代碼執(zhí)行結束”
5.主代碼宏任務結束被monitoring process進程監(jiān)聽到,主任務執(zhí)行Event Queue的微任務
6.微任務執(zhí)行完畢-“執(zhí)行then函數(shù)啦”
7.執(zhí)行宏任務console.log("定時器開始啦")
console.log("1"); // 1 6 7 2 4 5 9 10 11 8 3 // 記作 set1 setTimeout(function () { console.log("2"); // set4 setTimeout(function() { console.log("3"); }); // pro2 new Promise(function (resolve) { console.log("4"); resolve(); }).then(function () { console.log("5") }) }) // 記作 pro1 new Promise(function (resolve) { console.log("6"); resolve(); }).then(function () { console.log("7"); // set3 setTimeout(function() { console.log("8"); }); }) // 記作 set2 setTimeout(function () { console.log("9"); // 記作 pro3 new Promise(function (resolve) { console.log("10"); resolve(); }).then(function () { console.log("11"); }) })第一輪事件循環(huán)
1.整體script作為第一個宏任務進入主線程,遇到console.log,輸出1。
2.遇到set1,其回調函數(shù)被分發(fā)到宏任務Event Queue中。
3.遇到pro1,new Promise直接執(zhí)行,輸出6。then被分發(fā)到微任務Event Queue中。
4.遇到了set2,其回調函數(shù)被分發(fā)到宏任務Event Queue中。
主線程的整段js代碼(宏任務)執(zhí)行完,開始清空所有微任務;主線程執(zhí)行微任務pro1,輸出7;遇到set3,注冊回調函數(shù)。
第二輪事件循環(huán)1.主線程執(zhí)行隊列中第一個宏任務set1,輸出2;代碼中遇到了set4,注冊回調;又遇到了pro2,new promise()直接執(zhí)行輸出4,并注冊回調;
2.set1宏任務執(zhí)行完畢,開始清空微任務,主線程執(zhí)行微任務pro2,輸出5。
第三輪事件循環(huán)1.主線程執(zhí)行隊列中第一個宏任務set2,輸出9;代碼中遇到了pro3,new promise()直接輸出10,并注冊回調;
2.set2宏任務執(zhí)行完畢,開始情況微任務,主線程執(zhí)行微任務pro3,輸出11。
類似循環(huán)...
所以最后輸出結果為1、6、7、2、4、5、9、10、11、8、3。
參考文章https://www.cnblogs.com/Mainz...
http://www.ruanyifeng.com/blo...
https://juejin.im/post/5b4dfb...
https://juejin.im/post/5b879a...
https://juejin.im/post/59e85e...
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/54175.html
摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執(zhí)行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。執(zhí)行異步操作事件完成,回調函數(shù)進入。主線程從讀取回調函數(shù)并執(zhí)行。 最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址 了解進程和線程 進程是應用...
摘要:當這些異步任務發(fā)生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到運行時執(zhí)行線程空閑時候才會按照隊列先進先出的原則被一一執(zhí)行,但終究還是單線程。 瀏覽器是多進程的 showImg(https://segmentfault.com/img/remote/1460000019706956?w=815&h=517); Browser進程: 瀏覽器的主進程(負責協(xié)調、主控),只有一個。 負...
摘要:當這些異步任務發(fā)生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到運行時執(zhí)行線程空閑時候才會按照隊列先進先出的原則被一一執(zhí)行,但終究還是單線程。 瀏覽器是多進程的 showImg(https://segmentfault.com/img/remote/1460000019706956?w=815&h=517); Browser進程: 瀏覽器的主進程(負責協(xié)調、主控),只有一個。 負...
摘要:檢查宏任務隊列,發(fā)現(xiàn)有的回調函數(shù)立即執(zhí)行回調函數(shù)輸出。接著遇到它的作用是在后將回調函數(shù)放到宏任務隊列中這個任務在再下一次的事件循環(huán)中執(zhí)行。 為什么會寫這篇博文呢? 前段時間,和頭條的小伙伴聊天問頭條面試前端會問哪些問題,他稱如果是他面試的話,event-loop肯定是要問的。那天聊了蠻多,event-loop算是給我留下了很深的印象,原因很簡單,因為之前我從未深入了解過,如果是面試的時...
摘要:圖數(shù)據(jù)類型圖引用類型深淺拷貝問題不知道什么是深拷貝和淺拷貝的請先去并在調試臺自己操作一下,這篇文章只會說明為何中會有這種問題。所以有的時候我們?yōu)榱吮苊鉁\拷貝,會用一些方式實現(xiàn)深拷貝。 首先要了解的js基礎 基本數(shù)據(jù)類型:Object、undefined、null、Boolean、Number、String、Symbol (ES6新加) Object包括: Array 、Date 、R...
閱讀 3669·2021-11-18 10:02
閱讀 1040·2021-09-04 16:48
閱讀 2092·2019-08-30 15:55
閱讀 3652·2019-08-30 15:52
閱讀 1890·2019-08-30 14:08
閱讀 3635·2019-08-30 13:19
閱讀 1228·2019-08-27 10:53
閱讀 3213·2019-08-26 12:11