国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

瀏覽器知識

Pluser / 2224人閱讀

摘要:瀏覽器的渲染進程是多線程的。異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。

[TOC]

瀏覽器進程線程 區分線程和進程
 **- 什么是進程**

狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。

廣義定義:進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。

**- 什么是線程**

線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。

在當代面向線程設計的計算機結構中,進程是線程的容器。

一個進程有一個或多個線程,線程之間共同完成進程分配下來的任務。先看個形象的比喻:

- 進程是一個工廠,工廠有它的獨立資源

- 工廠之間相互獨立

- 線程是工廠中的工人,多個工人協作完成任務

- 工廠內有一個或多個工人

- 工人之間共享空間

再完善完善概念:

- 工廠的資源 -> 系統分配的內存(獨立的一塊內存)

- 工廠之間的相互獨立 -> 進程之間相互獨立

- 多個工人協作完成任務 -> 多個線程在進程中協作完成任務

- 工廠內有一個或多個工人 -> 一個進程由一個或多個線程組成

- 工人之間共享空間 -> 同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)

然后再鞏固下:

可以打開任務管理器,可以看到有一個后臺進程列表。這里就是查看進程的地方,而且可以看到每個進程的內存資源信息以及cpu占有率。

進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位),線程是cpu調度的最小單位(線程是建立在進程的基礎上的一次程序運行單位)。

一般通用的說法:單線程與多線程,都是指在一個進程內的單和多。(所以核心還是得屬于一個進程才行)

瀏覽器是多線程的

理解了進程與線程了區別后,接下來對瀏覽器進行一定程度上的認識:(先看下簡化理解)

瀏覽器是多進程的

瀏覽器之所以能夠運行,是因為系統給它的進程分配了資源(cpu、內存)

簡單點理解,每打開一個Tab頁,就相當于創建了一個獨立的瀏覽器進程。

圖中打開了Chrome瀏覽器的多個標簽頁,然后可以在Chrome的任務管理器中看到有多個進程(分別是每一個Tab頁面有一個獨立的進程,以及一個主進程)。

注意:在這里瀏覽器應該也有自己的優化機制,有時候打開多個tab頁后,可以在Chrome任務管理器中看到,有些進程被合并了(譬如打開多個空白標簽頁后,會發現多個空白標簽頁被合并成了一個進程),所以每一個Tab標簽對應一個進程并不一定是絕對的。

瀏覽器都包含哪些進程?

除了瀏覽器的標簽頁進程之外,瀏覽器還有一些其他進程來輔助支撐標簽頁的進程,主要包含哪些進程:(為了簡化理解,僅列舉主要進程)

Browser進程:瀏覽器的主進程(負責協調、主控),只有一個。作用有

負責瀏覽器界面顯示,與用戶交互。如前進,后退等

負責各個頁面的管理,創建和銷毀其他進程

將Renderer進程得到的內存中的Bitmap,繪制到用戶界面上

網絡資源的管理,下載等

第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建

GPU進程:最多一個,用于3D繪制等

瀏覽器渲染進程(瀏覽器內核)(Renderer進程,內部是多線程的):默認每個Tab頁面一個進程,互不影響。主要作用為

頁面渲染,腳本執行,事件處理等

強化記憶:在瀏覽器中打開一個網頁相當于新起了一個進程(進程內有自己的多線程)

瀏覽器多進程的優勢

相比于單進程瀏覽器,多進程有如下優點:

避免單個page crash影響整個瀏覽器

避免第三方插件crash影響整個瀏覽器

多進程充分利用多核優勢

方便使用沙盒模型隔離插件等進程,提高瀏覽器穩定性

簡單點理解:如果瀏覽器是單進程,那么某個Tab頁崩潰了,就影響了整個瀏覽器,體驗有多差;同理如果是單進程,插件崩潰了也會影響整個瀏覽器;

重點是瀏覽器內核(渲染進程)

瀏覽器內核,即我們的渲染進程,有名Renderer進程,我們頁面的渲染,js的執行,事件的循環都在這一進程內進行,也就是說,該進程下面擁有著多個線程,靠著這些現成共同完成渲染任務。

對于普通的前端操作來說,頁面的渲染,JS的執行,事件的循環,都在這個進程內進行。瀏覽器的渲染進程是多線程的。

渲染進程包含哪些主要的線程?

1.GUI渲染線程【圖形用戶界面(Graphical User Interface,簡稱 GUI,又稱圖形用戶接口)】

負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,布局和繪制等。

當界面需要重繪(Repaint)或由于某種操作引發回流(reflow)時,該線程就會執行

注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當于被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執行。

2.JS引擎線程

也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎)

JS引擎線程負責解析Javascript腳本,運行代碼。

JS引擎一直等待著任務隊列中任務的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序

同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。

3.事件觸發線程

歸屬于瀏覽器而不是JS引擎,用來控制事件循環(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協助)

當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中

當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理(事件循環 Event Loop)

注意,由于JS的單線程關系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執行)

4.定時觸發器線程

傳說中的setInterval與setTimeout所在線程

瀏覽器定時計數器并不是由JavaScript引擎計數的,(因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態就會影響記計時的準確)

因此通過多帶帶線程來計時并觸發定時【計時完畢后,添加到事件隊列中,等待JS引擎空閑后執行,這也是“JavaScript定時器不準確”的原因(可用requestAnimationFrame)】

注意,W3C在HTML標準中規定,規定要求setTimeout中低于4ms的時間間隔算為4ms。

5.異步http請求線程

在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求

將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。

為什么JS引擎是單線程的?為什么需要異步? 單線程又是如何實現異步的呢? 查看【鏈接描述】

Browser進程和瀏覽器內核(Renderer進程)的通信過程

如果自己打開任務管理器,然后打開一個瀏覽器,就可以看到:任務管理器中出現了兩個進程(一個是主控進程,一個則是打開Tab頁的渲染進程),
然后在這前提下,看下整個的過程:(簡化了很多)

Browser進程收到用戶請求,首先需要獲取頁面內容(譬如通過網絡下載資源),隨后將該任務通過RendererHost接口傳遞給Render進程

Renderer進程的Renderer接口收到消息,簡單解釋后,交給渲染線程,然后開始渲染

渲染線程接收請求,加載網頁并渲染網頁,這其中可能需要Browser進程獲取資源和需要GPU進程來幫助渲染

當然可能會有JS線程操作DOM(這樣可能會造成回流并重繪)

最后Render進程將結果傳遞給Browser進程

Browser進程接收到結果并將結果繪制出來

這里繪一張簡單的圖:(很簡化)

為什么JS引擎是單線程的

JavaScript作為一門客戶端的腳本語言,主要的任務是處理用戶的交互,而用戶的交互無非就是響應DOM的增刪改,使用事件隊列的形式,一次事件循環只處理一個事件響應,使得腳本執行相對連續。如果JS引擎被設計為多線程的,那么DOM之間必然會存在資源競爭,那么語言的實現會變得非常臃腫,在客戶端跑起來,資源的消耗和性能將會是不太樂觀的,故設計為單線程的形式,并附加一些其他的線程來實現異步的形式,這樣運行成本相對于使用JS多線程來說降低了很多。

梳理瀏覽器內核中線程之間的關系 GUI渲染線程與JS引擎線程互斥

由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和UI線程同時運行),那么渲染線程前后獲得的元素數據就可能不一致了。

因此為了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎為互斥的關系,當JS引擎執行時GUI線程會被掛起,
GUI更新則會被保存在一個隊列中等到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樹,這種做法確實對性能好一點。

JS引擎線程與事件觸發線程、定時觸發器線程、異步HTTP請求線程

事件觸發線程、定時觸發器線程、異步HTTP請求線程三個線程有一個共同點,那就是使用回調函數的形式,當滿足了特定的條件,這些回調函數會被執行。這些回調函數被瀏覽器內核理解成事件,在瀏覽器內核中擁有一個事件隊列,這三個線程當滿足了內部特定的條件,會將這些回調函數添加到事件隊列中,等待JS引擎空閑執行。例如異步HTTP請求線程,線程如果檢測到請求的狀態變更,如果設置有回調函數,回調函數會被添加事件隊列中,等待JS引擎空閑了執行。
但是,JS引擎對事件隊列(宏任務)與JS引擎內的任務(微任務)執行存在著先后循序,當每執行完一個事件隊列的時間,JS引擎會檢測內部是否有未執行的任務,如果有,將會優先執行(微任務)。

WebWorker

因為JS引擎是單線程的,當JS執行時間過長會頁面阻塞,那么JS就真的對CPU密集型計算無能為力么?

所以,后來HTML5中支持了 Web Worker。

來自MDN的官方解釋

Web Workers 使得一個Web應用程序可以在與主執行線程分離的后臺線程中運行一個腳本操作。這樣做的好處是可以在一個多帶帶的線程中執行費時的處理任務,從而允許主(通常是UI)線程運行而不被阻塞/放慢。

這樣理解下:

創建Worker時,JS引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作DOM
JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對象來與線程交互特定的數據)

所以,如果有非常耗時的工作,請多帶帶開一個Worker線程,這樣里面不管如何翻天覆地都不會影響JS引擎主線程,只待計算出結果后,將結果通信給主線程即可,perfect!

而且注意下,JS引擎是單線程的,這一點的本質仍然未改變,Worker可以理解是瀏覽器給JS引擎開的外掛,專門用來解決那些大量計算問題。

注意點:

WebWorker可以想瀏覽器申請一個子線程,該子線程服務于主線程,完全受主線程控制。

JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對象來與線程交互特定的數據)

所以,如果需要進行一些高耗時的計算時,可以多帶帶開啟一個WebWorker線程,這樣不管這個WebWorker子線程怎么密集計算、怎么阻塞,都不會影響JS引擎主線程,只需要等計算結束,將結果通過postMessage傳輸給主線程就可以了。

另外,還有個東西叫 SharedWorker,與WebWorker在概念上所不同。

WebWorker 只屬于某一個頁面,不會和其他標簽頁的Renderer進程共享,WebWorker是屬于Renderer進程創建的進程。所以ChromeRender進程中(每一個Tab頁就是一個render進程)創建一個新的線程來運行Worker中的JavaScript程序。

SharedWorker 是由瀏覽器多帶帶創建的進程來運行的JS程序,它被所有的Renderer進程所共享,在瀏覽器中,最多只能存在一個SharedWorker進程。

SharedWorker由進程管理,WebWorker是某一個Renderer進程下的線程。

看到這里,應該就很容易明白了,本質上就是進程和線程的區別。SharedWorker由獨立的進程管理,WebWorker只是屬于render進程下的一個線程

瀏覽器的渲染流程

每個瀏覽器內核的渲染流程不一樣,下面我們主要以webkit為主。
首先是渲染的前奏:

瀏覽器輸入url,瀏覽器主進程接管,開了一個下載線程

然后進行HTTP請求(DNS查詢、IP尋址等等),等待響應,開始下載響應報文。

將下載完的內容轉交給Renderer進程管理

開始渲染...

在說渲染之前,需要理解一些概念:

DOM Tree: 瀏覽器將HTML解析成樹形的數據結構。

CSS Rule Tree:瀏覽器將CSS解析成樹形的數據結構。

Render Tree:DOM樹和CSS規則樹合并后生產Render樹。

layout:有了Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關系,從而去計算出每個節點在屏幕中的位置。

painting: 按照算出來的規則,通過顯卡,把內容畫到屏幕上。

reflow(回流):當瀏覽器發現某個部分發生了點變化影響了布局,需要倒回去重新渲染,內行稱這個回退的過程叫 reflow。reflow 會從 這個 root frame 開始遞歸往下,依次計算所有的結點幾何尺寸和位置。reflow 幾乎是無法避免的。現在界面上流行的一些效果,比如樹狀目錄的折疊、展開(實質上是元素的顯 示與隱藏)等,都將引起瀏覽器的 reflow。鼠標滑過、點擊……只要這些行為引起了頁面上某些元素的占位面積、定位方式、邊距等屬性的變化,都會引起它內部、周圍甚至整個頁面的重新渲 染。通常我們都無法預估瀏覽器到底會 reflow 哪一部分的代碼,它們都彼此相互影響著。

repaint(重繪):改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部布局的屬性時,屏幕的一部分要重畫,但是元素的幾何尺寸沒有變。

注意:display:none的節點不會被加入Render Tree,而visibility: hidden則會,所以display:none會觸發reflowvisibility: hidden會觸發repaint

瀏覽器內核拿到響應報文之后,渲染大概分為以下步驟

解析html生產DOM樹。

解析CSS規則。

根據DOM Tree和CSS Tree生成Render Tree。

根據Render樹進行layout,負責各個元素節點的尺寸、位置計算。

繪制Render樹(painting),繪制頁面像素信息。

瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上。

詳細步驟略去,大概步驟如下,渲染完畢后JS引擎開始執行load事件,繪制流程見下圖。

由圖中可以看出,css在加載過程中不會影響到DOM樹的生成,但是會影響到Render樹的生成,進而影響到layout,所以一般來說,style的link標簽需要盡量放在head里面,因為在解析DOM樹的時候是自上而下的,而css樣式又是通過異步加載的,這樣的話,解析DOM樹下的body節點和加載css樣式能盡可能的并行,加快Render樹的生成的速度,當然,如果css是通過js動態添加進來的,會引起頁面的重繪或重新布局。
從有html標準以來到目前為止(2017年5月),標準一直是規定style元素不應出現在body元素中。

前面提到了load事件,那么與DOMContentLoaded事件有什么分別。

當 DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片。 (譬如如果有async加載的腳本就不一定完成)

當 onLoad 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片都已經加載完成了。 (渲染完畢了)

順序是:DOMContentLoaded -> load

普通圖層和復合圖層

渲染步驟就提到了composite概念;瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復合圖層。

普通文檔流內可以理解為一個復合圖層(這里默認復合層,里面不管添加多少元素,其實都是在同個復合圖層中)

absolute布局(fixed也一樣),雖然可以脫離文檔流,但它仍然屬于默認復合層

可以通過硬件加速的方式,聲明一個新的復合圖層,它會多帶帶分配資源(當然也會脫離普通文檔流,這樣一來,不管這個復合圖層中怎么變化,也不會影響默認復合層里的回流重繪)

可以簡單理解下:GPU中,各個復合圖層是多帶帶繪制的,所以互不影響,這也是為什么某些場景硬件加速效果一級棒

如何變成復合圖層(硬件加速)

將元素變成一個復合圖層,就是傳說中的硬件加速技術

最常用的方式:translate3d,translatez

opacity屬性/過渡動畫(需要動畫執行的過程中才會創建合成層,動畫沒有開始或結束后元素還會回到之前的狀態)

will-chang屬性(這個比較偏僻),一般配合opacitytranslate使用(而且經測試,除了上述可以引發硬件加速的屬性外,其它屬性并不會變成復合層),作用是提前告訴瀏覽器要變化,這樣瀏覽器會開始做一些優化工作(這個最好用完后就釋放)