進程和線程的概念能夠這樣理解:css
進程是一個工廠,工廠有它的獨立資源--工廠之間相互獨立--線程是工廠中的工人,多個工人協做完成任務--工廠內有一個或多個工人--工人之間共享空間
工廠有多個工人,就至關於一個進程能夠有多個線程,並且線程共享進程的空間。html
進程是cpu
資源分配的最小單位(是能擁有資源和獨立運行的最小單位,系統會給它分配內存)
線程是cpu
調試的最小單位(線程是創建在進程的基礎上的一次程序運行單位,一個進程中能夠有多個線程。核心仍是屬於一個進程。)前端
瀏覽器是多進程的,每打開一個tab
頁,就至關於建立了一個獨立的瀏覽器進程。ios
Browser
進程:瀏覽器的主進程(負責協調,主控),只有一個,做用有:es6
Rendered
進程獲得的內存中的Bitmap
,繪製到用戶界面上GPU
進程:最多一個,用於3D
繪製等。瀏覽器渲染進程(瀏覽器內核)(Render
進程,內部是多線程的):默認每一個Tab
頁面一個進程,互不影響。主要做用爲:web
在瀏覽器中打開一個網頁至關於新起了一個進程(進程內有本身的多線程)ajax
page crash
影響整個瀏覽器crash
影響整個瀏覽器簡單理解就是:若是瀏覽器是單進程的,某個Tab
頁崩潰了,就影響了整個瀏覽器,體驗就會不好。同理若是是單進程的,插件崩潰了也會影響整個瀏覽器;
固然,內存等資源消耗也會更大,像空間換時間同樣。canvas
對於普通的前端操做來講,最重要的渲染進程:頁面的渲染,js
的執行,事件的循環等都在這個進程內執行;api
瀏覽器是多進程的,瀏覽器的渲染進程是多線程的;promise
GUI
渲染線程HTML
,CSS
,構建DOM
樹和RenderObject
樹,佈局和繪製等。GUI
渲染線程與JS
引擎線程是互斥的,當JS
引擎執行時GUI
線程會被掛起(至關於凍結了),GUI
更新會被保存在一個隊列中等到JS
引擎空閒時當即被執行。JS
引擎線程JS
內核,負責處理JavaScript
腳本程序。(例如V8
引擎)。JS
引擎線程負責解析JavaScript
腳本,運行代碼。JS
引擎一直等待着任務隊列中任務的到來,而後加以處理,一個Tab
頁(render
進程)中不管何時都只有一個JS
線程在運行JS
程序。GUI
渲染線程與JS
引擎線程是互斥的,因此若是JS
執行的時間過長,這樣就會形成頁面的渲染不連貫,致使頁面渲染加載阻塞。JS
引擎,用來控制事件循環(能夠理解成JS
引擎本身都忙不過來,須要瀏覽器另開線程協助)。JS
引擎執行代碼塊如setTimeout
時(也可來自瀏覽器內核的其它線程,如鼠標點擊,AJAX
異步請求等),會將對應任務添加到事件線程中。JS
引擎的處理。JS
的單線程關係,因此這些待處理隊列中的事件都得排隊等待JS
引擎處理(當JS
引擎空閒時纔會去執行)。setTimeout
和setInterval
所在的線程JavaScript
引擎計數的,(由於JavaScript
引擎是單線程的,若是處於阻塞線程狀態就會影響計時的準確)JS
引擎空閒後執行)W3C
在HTML
標準中規定,規定要求setTimeout
中低於4ms
的時間間隔算爲4ms
。http
請求線程XMLHttpRequest
在鏈接後是經過瀏覽器新型一個線程請求JavaScript
引擎執行總結下來,渲染進程以下:
打開一個瀏覽器,能夠看到:任務管理器出現了2個進程(一個主進程,一個是打開Tab
頁的渲染進程);
Browser
主進程收到用戶請求,首先須要獲取頁面內容(如經過網絡下載資源),隨後將該任務經過RendererHost
接口傳遞給Render
渲染進程Render
渲染進程的Renderer
接口收到消息,簡單解釋後,交給渲染線程GUI
,而後開始渲染GUI
渲染線程接收請求,加載網頁並渲染網頁,這其中可能須要Browser
主進程獲取資源和須要GPU
進程來幫助渲染JS
線程操做DOM
(這可能會形成迴流並重繪)Render
渲染進程將結果傳遞給Browser
主進程Browser
主進程接收到結果並將結果繪製出來GUI渲染線程與JS引擎線程互斥
因爲JavaScript
是可操做DOM
的,若是在修改這些元素屬性同時渲染界面(即JS
線程和GUI
線程同時運行),那麼渲染線程先後得到的元素數據就可能不一致了。
所以,爲了防止渲染出現不可預期的結果,瀏覽器就設置了互斥的關係,當JS
引擎執行時GUI
線程會被掛起。GUI
更新則會被保存在一個隊列中等到JS
引擎線程空閒時當即被執行。
JS阻塞頁面加載
從上述的互斥關係,能夠推導出,JS
若是執行時間過長就會阻塞頁面。
譬如,假設JS
引擎正在進行巨量的計算,此時就算GUI
有更新,也會被保存在隊列中,要等到JS
引擎空閒後執行。而後因爲巨量計算,因此JS
引擎可能好久好久才能空閒,確定就會感受很卡。
因此,要儘可能避免JS
執行時間過長,這樣就會形成頁面的渲染不連貫,致使頁面渲染加載阻塞的感受。
css
加載是否會阻塞dom
樹渲染
這裏說的是頭部引入css
的狀況
首先,咱們都知道:css
是由單獨的下載線程異步下載的。
而後還有幾個現象:
css
加載不會阻塞DOM
樹解析(異步加載時dom
照常構建)render
樹渲染(渲染時須要等css
加載完畢,由於render
樹須要css
信息)這可能也是瀏覽器的一種優化機制
由於你加載css
的時候,可能會修改下面DOM
節點的樣式,若是css
加載不阻塞render
樹渲染的話,那麼當css
加載完以後,render
樹可能又得從新重繪或者回流了,這就形成了一些沒有必要的損耗
因此乾脆把DOM
樹的結構先解析完,把能夠作的工做作完,而後等css
加載完以後,在根據最終的樣式來渲染render
樹,這種作法確實對性能好一點。
WebWorker
,JS
的多線程?
前文中有提到JS
引擎是單線程的,並且JS
執行時間過長會阻塞頁面,那麼JS
就真的對cpu
密集型計算無能爲力麼?
因此,後來HTML5
中支持了WebWorker
。
這樣理解下:
建立Worker
時,JS
引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,徹底受主線程控制,並且不能操做DOM
)JS
引擎線程與worker
線程間經過特定的方式通訊(postMessage API
,須要經過序列化對象來與線程交互特定的數據)
因此,若是有很是耗時的工做,請單獨開一個Worker
線程,這樣裏面無論如何翻天覆地都不會影響JS
引擎主線程,只待計算出結果後,將結果通訊給主線程便可,perfect!
並且注意下,JS
引擎是單線程的,這一點的本質仍然未改變,Worker
能夠理解是瀏覽器給JS
引擎開的外掛,專門用來解決那些大量計算問題。
WebWorker
與SharedWorker
既然都到了這裏,就再提一下SharedWorker
(避免後續將這兩個概念搞混)
WebWorker
只屬於某個頁面,不會和其餘頁面的Render
進程(瀏覽器內核進程)共享
因此Chrome
在Render
進程中(每個Tab
頁就是一個render
進程)建立一個新的線程來運行Worker
中的JavaScript
程序。
SharedWorker
是瀏覽器全部頁面共享的,不能採用與Worker
一樣的方式實現,由於它不隸屬於某個Render
進程,能夠爲多個Render
進程共享使用
因此Chrome
瀏覽器爲SharedWorker
單首創建一個進程來運行JavaScript
程序,在瀏覽器中每一個相同的JavaScript
只存在一個SharedWorker
進程,無論它被建立多少次。
看到這裏,應該就很容易明白了,本質上就是進程和線程的區別。SharedWorker
由獨立的進程管理,WebWorker
只是屬於render
進程下的一個線程
瀏覽器輸入url
,瀏覽器主進程接管,開一個下載線程,而後進行http
請求(略去DNS
查詢,IP
尋址等等操做),而後等待響應,獲取內容,隨後將內容經過RendererHost
接口轉交給Render
進程--瀏覽器渲染流程開始
瀏覽器內核拿到內容後,渲染大概能夠劃分爲:
html
創建dom
要css
構建render
樹(將css
代碼解析成樹形的數據結構,而後結合dom
合併成render
樹)render
樹(Layout/reflow
),負責各元素尺寸,位置的計算render
樹(paint
),繪製頁面像素信息GPU
,GPU
會將各層合成(composite
),顯示在屏幕上渲染完畢後就是load
事件了,以後就是本身的JS
邏輯處理了,略去了詳細步驟。
load
事件與DOMContentLoaded
事件的前後
上面提到,渲染完畢後會觸發load
事件,那麼你能分清楚load
事件與DOMContentLoaded
事件的前後麼?
很簡單,知道它們的定義就能夠了:
當 DOMContentLoaded
事件觸發時,僅當DOM
加載完成,不包括樣式表,圖片。
(譬如若是有async
加載的腳本就不必定完成)
當 onload
事件觸發時,頁面上全部的DOM
,樣式表,腳本,圖片都已經加載完成了。(渲染完畢了)
因此,順序是:DOMContentLoaded
-> load
渲染步驟就提到了composite
概念;瀏覽器渲染的圖層通常包含兩大類:普通圖層以及複合圖層。
absolute
佈局(fixed
也同樣),雖然能夠脫離文檔流,但它仍然屬於默認複合層能夠簡單理解下:GPU
中,各個複合圖層是單獨繪製的,因此互不影響,這也是爲何某些場景硬件加速效果一級棒
如何變成複合圖層(硬件加速)
將元素變成一個複合圖層,就是傳說中的硬件加速技術
translate3d
,translatez
opacity
屬性/過渡動畫(須要動畫執行的過程當中纔會建立合成層,動畫沒有開始或結束後元素還會回到以前的狀態)will-chang
屬性(這個比較偏僻),通常配合opacity
與translate
使用(並且經測試,除了上述能夠引起硬件加速的屬性外,其它屬性並不會變成複合層),做用是提早告訴瀏覽器要變化,這樣瀏覽器會開始作一些優化工做(這個最好用完後就釋放)<video><iframe><canvas><webgl>
等元素flash
插件absolute
和硬件加速的區別
能夠看到,absolute
雖然能夠脫離普通文檔流,可是沒法脫離默認複合層。
因此,就算absolute
中信息改變時不會改變普通文檔流中render
樹,可是,瀏覽器最終繪製時,是整個複合層繪製的,因此absolute
中信息的改變,仍然會影響整個複合層的繪製。(瀏覽器會重繪它,若是複合層中內容多,absolute
帶來的繪製信息變化過大,資源消耗是很是嚴重的)
而硬件加速直接就是在另外一個複合層了(另起爐竈),因此它的信息改變不會影響默認複合層(固然了,內部確定會影響屬於本身的複合層),僅僅是引起最後的合成(輸出視圖)
複合圖層的做用
通常一個元素開啓硬件加速後會變成複合圖層,能夠獨立於普通文檔流中,改動後能夠避免整個頁面重繪,提高性能。
可是儘可能不要大量使用複合圖層,不然因爲資源消耗過分,頁面反而會變的更卡。
硬件加速時請使用index
使用硬件加速時,儘量的使用index,防止瀏覽器默認給後續的元素建立複合層渲染
具體的原理是:webkit CSS3
中,若是這個元素添加了硬件加速,而且index
層級比較低,那麼在這個元素的後面其它元素(層級比這個元素高的,或者相同的,而且relective
或absolute
屬性相同的),會默認變爲複合層渲染,若是處理不當會極大的影響性能
簡單點理解,能夠認爲是一個隱式合成的概念:若是a是一個複合層,並且b在a上面,那麼b也會被隱式轉爲一個複合圖層,這點須要特別注意
Event Loop
談JS
的運行機制到此時,已是屬於瀏覽器頁面初次渲染完畢後的事情,JS
引擎的一些運行機制分析。主要是結合Event Loop
來談JS
代碼是如何執行的。
咱們已經知道了JS
引擎是單線程的,知道了JS
引擎線程,事件觸發線程,定時觸發器線程。
而後還須要知道:
JS
分爲同步任務和異步任務JS
引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧,開始執行。看到這裏,應該就能夠理解了:爲何有時候setTimeOut
推入的事件不能準時執行?由於可能在它推入到事件列表時,主線程還不空閒,正在執行其它代碼,因此就必須等待,天然有偏差。
主線程在運行時會產生執行棧,棧中的代碼調用某些api
時,它們會在事件隊列中添加各類事件(當知足觸發條件後,如ajax
請求完畢)。而當棧中的代碼執行完畢,就會去讀取事件隊列中的事件,去執行那些回調,如此循環。
上面事件循環機制的核心是:JS
引擎線程和事件觸發線程
調用setTimeout
後,是由定時器線程控制等到特定時間後添加到事件隊列的,由於JS
引擎是單線程的,若是處於阻塞線程狀態就會影響計時準確,所以頗有必要另開一個線程用來計時。
當使用setTimout
或setInterval
時,須要定時器線程計時,計時完成後就會將特定的事件推入事件隊列中。
如:
setTimeout(()=>console.log('hello!),1000) //等1000毫秒計時完畢後(由定時器線程計時),將回調函數推入事件隊列中,等待主線程執行 setTimeout(()=>{ console.log('hello') },0) console.log('begin')
這段代碼的效果是最快的時間內將回調函數推入事件隊列中,等待主線程執行。
注意:
begin
,後hello
0
毫秒就推入事件隊列,可是W3C
在HTML
標準中規定,規定要求setTimeout
中低於4ms
的時間間隔算爲4ms
4ms
,就算假設0
毫秒就推入事件隊列,也會先執行begin
(由於只能可執行棧內空了後纔會主動讀取事件隊列)setInterval
用setTimeout
模擬按期計時和直接用setInterval
是有區別的:
setTimeout
計時到後就會去執行,而後執行一段時間後纔會繼續setTimeout
,中間就多了偏差setInterval
則是每次都精確的隔一段時間推入一個事件(可是,事件的實際執行時間不必定就準確,還有多是這個事件還沒執行完畢,下一個事件就來了)並且setInterval
有一些比較致命的問題:
setInterval
代碼在setInterval
再次添加到隊列以前尚未完成執行,就會致使定時器代碼連續運行好幾回,而之間沒有間隔,就算正常間隔執行,多個setInterval
的代碼執行時間可能會比預期小(由於代碼執行須要必定時間)ios
的webview
,或者safari
等瀏覽器中都有一人特色,在滾動的時候是不執行JS
的,若是使用了setInterval
,會發如今滾動結束後會執行屢次因爲滾動不執行JS
積攢回調,若是回調執行時間過長,就會很是容易形成卡頓問題和一些不可知的錯誤(setInterval
自帶的優化,若是當前事件隊列中有setInterval
的回調,不會重複添加回調)setInterval
並非不執行程序,它會把setInterval
的回調函數放在隊列中,等瀏覽器窗口再次打開時,一瞬間所有執行因此,至於這麼問題,通常認爲的最佳方案是:用setTimeout
模擬setInterval
或者特殊場合直接用requestAnimationFrame
Promise
時代的microtask
與macrotask
在es6
盛行的如今,能夠看下這題:
console.log('script start'); setTimeout(()=>{ console.log('setTimeout') },0); Promise.resolve() .then(()=>console.log('promise1')) .then(()=>console.log('promise2')) console.log('script end') //執行結果: script start script end promise1 promise2 setTimeout
由於promise
有一個新的概念microtask
.或者能夠說JS
中分爲兩種任務:macrotask
和microtask
;
理解以下:
macrotask
(又叫宏任務),主代碼塊,setTimeout
,setInterval
等(能夠看到,事件隊列中的每個事件都是一個macrotask
)macrotask
會從頭至尾將這個任務執行完畢,不會執行其它JS
內部macrotask
與DOM
任務可以有序的執行,會在一個macrotask
執行結束後,在下一個macrotask
執行開始前,對頁面進行從新渲染(task
->渲染->task
->...)microtask
(又叫微任務),Promise
,process.nextTick
等。macrotask
執行結束後當即執行的任務macrotask
任務後,下一個macrotask
以前,在渲染以前setTimeout
(setTimeout
是macrotask
)會更快由於無需等待渲染macrotask
執行完成後,就會將在它執行期間產生的全部microtask
都執行完畢(在渲染前)注意:在Node
環境下,process.nextTick
的優先級高於promise
.也就是:在宏任務結束後會先執行微任務隊列中的nextTick
部分,而後纔會執行微任務中的promise
部分。
另外,setImmediate
則是規定:在下一次Event Loop
(宏任務)時觸發(因此它是屬於優先級較高的宏任務),(Node.js
文檔中稱,setImmediate
指定的回調函數,老是排在setTimeout
前面),因此setImmediate
若是嵌套的話,是須要通過多個Loop
才能完成的,而不會像process.nextTick
同樣沒完沒了。
能夠理解:
macrotask
中的事件都是放在一個事件隊列中的,而這個隊列由事件觸發線程維護.microtask
中的全部微任務都是添加到微任務隊列中,等待當前macrotask
執行完後執行,而這個隊列由JS
引擎線程維護。因此:
JS
線程繼續接管,開始下一個宏任務(從事件隊列中獲取)new Promise
裏的函數是直接執行的算作主程序裏,並且.then
後面的纔會放到微任務中。
另外,請注意下Promise
的polyfill
與官方版本的區別:
官方版本中,是標準的microtask
形式polyfill
,通常都是經過setTimeout
模擬的,因此是macrotask
形式
請特別注意這兩點區別
注意,有一些瀏覽器執行結果不同(由於它們可能把microtask
當成macrotask
來執行了),可是爲了簡單,這裏不描述一些不標準的瀏覽器下的場景(但記住,有些瀏覽器可能並不標準)
Mutation Observer
能夠用來實現microtask
(它屬於microtask
,優先級小於Promise
,通常是Promise
不支持時纔會這樣作)
它是HTML5
中的新特性,做用是:監聽一個DOM
變更,當DOM
對象樹發生任何變更時,Mutation Observer
會獲得通知
像之前的Vue
源碼中就是利用它來模擬nextTick
的,具體原理是,建立一個TextNode
並監聽內容變化,而後要nextTick
的時候去改一下這個節點的文本內容,以下:(Vue
的源碼,未修改)
var counter=1 var observer=newMutationObserver(nextTickHandler) var textNode=document.createTextNode(String(counter)) observer.observe(textNode,{characterData:true}) timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) }
不過,如今的Vue(2.5+)
的nextTick
實現移除了Mutation Observer
的方式(聽說是兼容性緣由),取而代之的是使用MessageChannel
(固然,默認狀況仍然是Promise
,不支持才兼容的)。
MessageChannel
屬於宏任務,優先級是:setImmediate->MessageChannel->setTimeout
,因此Vue(2.5+)
內部的nextTick
與2.4
及以前的實現是不同的,須要注意下。