一、進程和線程
1.應用程序是有一個或多個模塊組成的抹锄,以現(xiàn)在的谷歌瀏覽器舉例逆瑞,他有一個瀏覽器主進程、一個GPU進程祈远、一個網(wǎng)絡進程呆万、多個渲染進程和多個插件進程商源。
2.進程是由一個或者多個線程組成的车份,線程是進程的基本單位。
3.進程之間互相獨立牡彻,有自己的資源扫沼,比如CPU的時間片,占用的內存庄吼。
4.一個進程下的線程(工人)共享進程(工廠)的資源缎除,包括代碼段、數(shù)據(jù)段总寻、內存等器罐。
5.線程之間協(xié)作在進程中完成任務。
6.進程是CPU資源分配的最小單位渐行,是可以擁有資源和獨立運行的最小單位轰坊。
7.線程是CPU調度的最小單位铸董,因為進程可以有一個或者多個線程。
tips:瀏覽器打開多個便簽頁肴沫,都會增加進程數(shù)量粟害,比如我用IE瀏覽器打開百度、京東颤芬、淘寶悲幅。
二、瀏覽器進程與線程
瀏覽器是多進程的站蝠,其中包含了:
? ? ?1)Browser進程------主進程只有一個汰具,負責協(xié)調、控制菱魔。比如創(chuàng)建Tab頁面郁副,或者銷毀頁面、下載網(wǎng)絡資源等豌习。
? ? ?2)GPU進程------最多一個存谎,用于繪畫3D。
? ? ?3)第三方插件進程------每種類型的插件對應一個進程肥隆,使用一個就開辟一個進程既荚。
? ? ?4)網(wǎng)絡進程------網(wǎng)絡請求,返回數(shù)據(jù)
? ? ?5)瀏覽器渲染進程------默認每個Tab頁面一個進程栋艳,互不影響恰聘。
? ? ?這里面的進程很好理解,瀏覽器本身吸占,第三方插件擴容晴叨,瀏覽器渲染,GPU矾屯。其中兼蕊,瀏覽器渲染JS就是通過瀏覽器渲染進程進行的。
? ? ?瀏覽器渲染引擎是多線程的件蚕,其中包括以下線程:
? ? ?1)GUI渲染線程? ?--->? 界面渲染
????????GUI渲染線程主要負責渲染頁面的孙技,解析HTML、CSS排作,構建成一個DOM樹和render樹牵啦;當界面需要重繪或者重排引發(fā)回流時,這個線程會? ? ? ?????執(zhí)行妄痪。注意哈雏,GUI渲染線程和JS引擎執(zhí)行是互斥的,當js引擎執(zhí)行時,GUI線程會被掛起裳瘪,保存到一個任務隊列中履因,等到JS引擎線程空閑時? ? ? ?????候才會出隊被立即執(zhí)行。如果不互斥盹愚,你一邊用戶操作一遍渲染界面栅迄,會導致渲染不準確等,這就決定了JS是單線程的了皆怕。? ??????????
? ? ?2)JS引擎線程? ? ?--->? ?JS處理
? ??????負責處理js腳本毅舆,解析腳本運行代碼。但是js引擎會一直等待著任務隊列的到來愈腾,然后加以處理憋活。同樣,GUI渲染線程和JS引擎執(zhí)行是互斥? ?????????的虱黄,如果js執(zhí)行的時候太長悦即,就會導致頁面渲染不連貫,也就是阻塞頁面橱乱,導致了頁面渲染加載被阻塞了辜梳,這就是為什么常常不把script標簽 ????????放在頭部而放在body底部下的原因。
? ? ?3)事件觸發(fā)線程? --->? ?事件處理
? ? ?4)定時器線程? ? ? --->? ?定時器處理
? ? ?5)http異步請求線程? ? ?----> 異步請求處理
? ? ?一般我們前端所學的就是對以上線程的操作和處理泳叠,管理好以上線程對我們前端技術能夠有很大的提升作瞄。
browser主進程和其它進程的通信
rowser主進程收到用戶請求后,首先需要獲取頁面內容(譬如通過網(wǎng)絡進程下載資源)危纫,隨后將該任務通過RendererHost接口傳遞給資源Render進程
Renderer進程的Renderer接口收到消息宗挥,簡單解釋后,交給渲染線程种蝶,然后開始渲染
渲染線程接收請求契耿,加載網(wǎng)頁并渲染網(wǎng)頁,這其中可能需要Browser主進程獲取資源和需要GPU進程來幫助渲染螃征,這期間都是主進程調度協(xié)助進程完成的搪桂。
當然可能會有JS線程操作DOM(這樣可能會造成回流/重繪)
最后Render進程將結果傳遞給Browser進程
Browser主進程接收到每個協(xié)作進程處理好的結果后,將結果繪制出來
三会傲、梳理瀏覽器內核線程之間的關系
渲染進程锅棕,即瀏覽器內核,才是我們前端關注的重點淌山,再次強調一下。然后我們必須知道一下幾點顾瞻。
1.GUI線程和JS引擎線程是互斥的
2.因為第1點泼疑,如果JS引擎執(zhí)行時間很久,就會引發(fā)頁面阻塞
3.HTML5中支持了Web Worker荷荤,以解決頁面阻塞退渗,但是Web Worker是JS引擎子線程移稳,受JS引擎控制,JS單線程依舊不會改變
四会油、瀏覽器渲染流程
這個過程也很復雜个粱,可以參考我之前的計算機網(wǎng)絡(前端版)的問題,過多的細節(jié)不在這里描述翻翩。這里只做簡單的描述
1.瀏覽器輸入URL都许,瀏覽器主線程接管,開一個下載線程嫂冻,已進行http請求胶征。(忽略DNS等等)
2.拿到響應內容,將內容通過RendererHost接口轉交給Renderer渲染進程
3.瀏覽器開始渲染(可能會協(xié)調GPU等線程協(xié)作完成)
渲染進程(內核)拿到內容后桨仿,分以下幾步開始渲染:
1.解析HTML建立dom樹
2.解析CSS構建render樹睛低,其實就是講css解析成樹的數(shù)據(jù)結構,然后結合dom樹形成render樹服傍。
3.布局render樹(Layout/reflow)钱雷,復雜計算元素的大小、位置等信息
4.開始繪制render樹(paint)吹零,繪制頁面像素信息
5.瀏覽器會將繪制信息發(fā)送給GPU急波,GPU會將合成(composite),顯示在屏幕上瘪校。
注意細節(jié)
1.DOMContentLoaded 事件觸發(fā)時愉耙,僅當DOM加載完成,不包括樣式表红氯,圖片躺彬。有async的腳本也不一定完成。
2.當 onload 事件觸發(fā)時麻惶,頁面上所有的DOM馍刮,樣式表,腳本窃蹋,圖片都已經(jīng)加載完成了卡啰。頁面渲染完畢。
3.css加載不會阻塞DOM樹解析(異步加載時DOM照常構建)警没;但會阻塞render樹渲染(渲染時需等css加載完畢匈辱,因為render樹需要css信息)
4.渲染步驟中就提到了composite的概念,可以簡單的理解為杀迹,瀏覽器渲染圖層一般包括兩大類亡脸,就是普通圖層和復合圖層。
5.文檔就可以當做一個默認的復合圖層,其次absolute這樣的脫離文檔流也依然屬于復合圖層浅碾。硬件加速什么的這里就不展開描述了大州。
五、JavaScript引擎
avaScript引擎從頭到尾負責整個JavaScript程序的編譯和執(zhí)行過程垂谢。js的引擎有很多種厦画,而最為大家熟知的無疑是V8引擎,他用于Chrome瀏覽器和Node中滥朱。
V8引擎由兩個主要部件組成:
emory Heap(內存堆)?—?內存分配地址的地方
Call Stack(調用堆棧) — 代碼執(zhí)行的地方
js引擎執(zhí)行過程
全面分析js引擎的執(zhí)行過程根暑,主要分為三個階段:
1. 語法分析
2. 預編譯階段
3. 執(zhí)行階段
不同的運行環(huán)境執(zhí)行都會進入到代碼預編譯和執(zhí)行兩個階段,語法分析則在代碼塊加載完畢時統(tǒng)一檢查語法焚虱。
(一)語法分析
分析該js腳本代碼塊的語法是否正確购裙,如果出現(xiàn)不正確,則向外拋出一個語法錯誤(SyntaxError)鹃栽,停止該js代碼塊的執(zhí)行躏率,然后繼續(xù)查找并加載下一個代碼塊;如果語法正確民鼓,則進入預編譯階段
(二)預編譯階段
js代碼塊通過語法分析階段之后薇芝,語法都正確的下回進入預編譯階段。
在分析預編譯階段之前丰嘉,我們先來了解一下js的運行環(huán)境夯到,運行環(huán)境主要由三種:
1、全局環(huán)境(js代碼加載完畢后饮亏,進入到預編譯也就是進入到全局環(huán)境)
2耍贾、函數(shù)環(huán)境(函數(shù)調用的時候,進入到該函數(shù)環(huán)境路幸,不同的函數(shù)荐开,函數(shù)環(huán)境不同)
3、eval環(huán)境(不建議使用简肴,存在安全晃听、性能問題)
每進入到一個不同的運行環(huán)境都會創(chuàng)建一個相應的執(zhí)行上下文(execution context)「下文會介紹」,那么在一段js程序中一般都會創(chuàng)建多個執(zhí)行上下文砰识,js引擎會以棧的數(shù)據(jù)結構對這些執(zhí)行進行處理能扒,形成函數(shù)調用棧(call stack),棧底永遠是全局執(zhí)行上下文(global execution context)辫狼,棧頂則永遠時當前的執(zhí)行上下文初斑。
我們聲明的函數(shù)與變量被儲存在『內存堆』中,而當我們要執(zhí)行的時候予借,就必須借助于『調用椩狡剑』來解決問題频蛔。函數(shù)調用棧就是使用棧存取的方式進行管理運行環(huán)境灵迫,特點是先進后出秦叛,后進后出
1、首先進入到全局環(huán)境瀑粥,創(chuàng)建全局執(zhí)行上下文(global Execution Context )挣跋,推入到stack中;
2狞换、調用bar函數(shù)避咆,進入bar函數(shù)運行環(huán)境,創(chuàng)建bar函數(shù)執(zhí)行上下文(bar Execution Context)修噪,推入stack棧中查库;
3、在bar函數(shù)內部調用foo函數(shù)黄琼,則再進入到foo函數(shù)運行環(huán)境中樊销,創(chuàng)建foo函數(shù)執(zhí)行上下文(foo Execution Context),如上圖脏款,由于foo函數(shù)內部沒有再調用其他函數(shù)围苫,那么則開始出棧;
5撤师、foo函數(shù)執(zhí)行完畢之后剂府,棧頂foo函數(shù)執(zhí)行上下文(foo Execution Context)首先出棧;
6剃盾、bar函數(shù)執(zhí)行完畢腺占,bar函數(shù)執(zhí)行上下文(bar Execution Context)出棧;
7痒谴、全局上下文(global Execution Cntext)在瀏覽器或者該標簽關閉的時候出棧衰伯。
執(zhí)行上下文分為兩個階段:
創(chuàng)建階段
執(zhí)行階段
創(chuàng)建階段
創(chuàng)建執(zhí)行上下文的過程中,主要是做了下面三件事闰歪,如圖所示:
1嚎研、確定 this 的值,也被稱為 This Binding库倘。
2临扮、LexicalEnvironment(詞法環(huán)境) 組件被創(chuàng)建。
3教翩、VariableEnvironment(變量環(huán)境) 組件被創(chuàng)建杆勇。
This Binding
全局執(zhí)行上下文中,this的值指向全局對象饱亿,在瀏覽器中this的值指向window對象蚜退,而在nodejs中指向這個文件的module對象闰靴。
函數(shù)執(zhí)行上下文中,this的值取決于函數(shù)的調用方式钻注。具體有:默認綁定蚂且、隱式綁定、顯式綁定(硬綁定)幅恋、new綁定杏死、箭頭函數(shù),
詞法環(huán)境有兩個組成部分
1捆交、環(huán)境記錄:存儲變量和函數(shù)聲明的實際位置
????環(huán)境記錄有三種類型淑翼,分別是聲明式環(huán)境記錄(Declarative Environment Record)、對象式環(huán)境記錄(Object EnvironmentRecord)品追、????全局環(huán)境記錄(Global Environment Record)? ??
2玄括、對外部環(huán)境的引用:可以訪問其外部詞法環(huán)境
在初始化環(huán)境記錄遇到函數(shù)聲明時會創(chuàng)建一個內部函數(shù)對象,這個函數(shù)對象有一個scope屬性指向函數(shù)聲明所在的環(huán)境,如在該代碼示例中,掃描到bar()函數(shù)聲明時會在foo environment中創(chuàng)建一個內部函數(shù)對象,其scope屬性指向bar()函數(shù)聲明所在的foo environment,重要的一點是:當開始執(zhí)行bar()函數(shù)前初始化bar()函數(shù)的環(huán)境時,就把bar()函數(shù)的scope屬性指向的foo environment 賦值給bar環(huán)境的out reference作為其外部引用
外部詞法環(huán)境的引用將一個詞法環(huán)境和其外部詞法環(huán)境鏈接起來,外部詞法環(huán)境又擁有對其自身的外部詞法環(huán)境的引用肉瓦。這樣就形成一個鏈式結構遭京,這里我們稱其為環(huán)境鏈(即ES6之前的作用域鏈),全局環(huán)境是這條鏈的頂端风宁。
環(huán)境鏈的存在是為了標識符的解析洁墙,通俗的說就是查找變量。首先在當前環(huán)境查找變量戒财,找不到就去外部環(huán)境找热监,還找不到就去外部環(huán)境的外部環(huán)境找,以此類推饮寞,直到找到孝扛,或者到環(huán)境鏈頂端(全局環(huán)境)還未找到則拋出ReferenceError。
詞法環(huán)境有兩種類型
1幽崩、全局環(huán)境:是一個沒有外部環(huán)境的詞法環(huán)境苦始,其外部環(huán)境引用為null。擁有一個全局對象(window 對象)及其關聯(lián)的方法和屬性(例如數(shù)組方法)以及任何用戶自定義的全局變量慌申,this的值指向這個全局對象陌选。
2、函數(shù)環(huán)境:用戶在函數(shù)中定義的變量被存儲在環(huán)境記錄中蹄溉,包含了arguments對象咨油。對外部環(huán)境的引用可以是全局環(huán)境,也可以是包含內部函數(shù)的外部函數(shù)環(huán)境柒爵。
直接看偽代碼可能更加直觀
變量環(huán)境
變量環(huán)境也是一個詞法環(huán)境役电,因此它具有上面定義的詞法環(huán)境的所有屬性。
在 ES6 中棉胀,詞法環(huán)境和變量環(huán)境的區(qū)別在于前者用于存儲函數(shù)聲明和變量(let和const)綁定法瑟,而后者僅用于存儲變量(var)綁定冀膝。
使用例子進行介紹
變量提升的原因:在創(chuàng)建階段,函數(shù)聲明存儲在環(huán)境中霎挟,而變量會被設置為undefined(在var的情況下)或保持未初始化(在let和const的情況下)窝剖。所以這就是為什么可以在聲明之前訪問var定義的變量(盡管是undefined),但如果在聲明之前訪問let和const定義的變量就會提示引用錯誤的原因氓扛。這就是所謂的變量提升枯芬。
詞法分析:
第一步论笔,分析函數(shù)參數(shù):
形式參數(shù):AO.age = undefined
實參:AO.age = 18
第二步采郎,分析局部變量:
第3行代碼有var age,但此時第一步中已有AO.age = 18,故不做任何改變
即AO.age = 18
第三步,分析函數(shù)聲明:
第5行代碼有函數(shù)age狂魔,則將function age(){}付給AO.age,即AO.age = function age() {}
所以蒜埋,執(zhí)行代碼時:
第2行代碼運行時拿到的age是詞法分析后的AO.age,結果是:function age() {};
第3行代碼:25賦給age最楷,此時age=25整份;
第4行代碼運行時age已被賦值為25,結果25籽孙;
第5,6行代碼是一個函數(shù)表達式烈评,所以不會做任何操作;
第7行代碼運行時age仍然是25犯建,結果也是25讲冠。看看瀏覽器執(zhí)行的結果适瓦,bingo~~
六竿开、瀏覽器渲染流程
?在JS引擎線程中,可以分為同步和異步任務玻熙,其中:
1)同步任務全部通過主線程執(zhí)行否彩,形成?執(zhí)行棧
2)異步任務通過事件觸發(fā)線程或者定時器線程處理,形成?任務隊列
3)當執(zhí)行棧中的任務全部處理完成嗦随,主線程為空閑的時候列荔,會從任務隊列中提取任務到執(zhí)行棧中執(zhí)行。
在JS中又兩種任務枚尼,同步任務和異步任務贴浙,又有兩種任務類型?macrotask?和?microtask:
? ? ?1)macr0task: 宏任務,如主代碼塊任務姑原,setTimeout悬而,setInterval等,是從事件隊列中取一個事件回調放到執(zhí)行棧中執(zhí)行锭汛。
? ? ?2)microtask:微任務笨奠,如Promise袭蝗,Process.nextTick(),是執(zhí)行棧執(zhí)行完后立即執(zhí)行的任務。