www.ruanyifeng.com/blog/2014/1…javascript
www.zhihu.com/question/64…html
www.alloyteam.com/2016/05/jav…java
juejin.im/post/59e85e…canvas
首先明確一下兩個概念多線程
代碼寫的怎麼樣,頁面性能如何的直觀感受是頁面生成的快不快,這個與瀏覽器的渲染進程息息相關。下面先梳理一下異步
setInterval
與setTimeout
所在的線程setTimeout(fn,ms)
指定某個任務在主線程最先可得的空閒時間執行,ms
秒以後將fn
函數加入到隊列中setTimeout
中低於4ms
的時間間隔算爲4ms
。因此setTimeout
的延時設置爲0
也不可能瞬發因爲JavaScript是可操縱DOM的,若是在修改這些元素屬性同時渲染界面(即JS線程和UI線程同時運行),那麼渲染線程先後得到的元素數據就可能不一致了。 所以爲了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎爲互斥的關係,當JS引擎執行時GUI線程會被掛起,GUI更新則會被保存在一個隊列中等到JS引擎線程空閒時當即被執行。ide
頁面的解析工做是在 Renderer 進程中進行的,Renderer 進程經過在主線程中持有的 Blink 實例邊接收邊解析 HTML 內容。每次從網絡緩衝區中讀取 8KB 之內的數據。瀏覽器自上而下逐行解析 HTML 內容,通過詞法分析、語法分析,構建 DOM 樹。當遇到外部 CSS 連接時,主線程調使用網絡請求板塊異步獲取資源,不阻塞而繼續構建 DOM 樹。當 CSS 下載完畢後,主線程在合適的時機解析 CSS 內容,通過詞法分析、語法分析,構建 CSSOM 樹。瀏覽器結合 DOM 樹和 CSSOM 樹構建 Render 樹,並計算佈局屬性,每一個 Node 的幾何屬性和在座標系中的位置,最後進行繪製展示在屏幕上。當遇到外部 JS 連接時,主線程調使用網絡請求板塊異步獲取資源,由於 JS 可能會修改 DOM 樹和 CSSOM 樹而形成迴流和重繪,此時 DOM 樹的構建是處於阻塞狀態的。但主線程並不會掛起,瀏覽器會用一個輕量級的掃描器去發現後續須要下載的外部資源,提早發起網絡請求,而腳本內部的資源不會識別,比方 document.write。當 JS 下載完畢後,瀏覽器調使用 V8 引擎在 Script Streamer 線程中解析、編譯 JS 內容,並在主線程中執行。
當 DOM 樹構建完畢後,還需通過好幾迴轉換,它們有多種中間表示。首先計算佈局、繪圖樣式,轉換爲 RenderObject 樹(也叫 Render 樹)。再轉換爲 RenderLayer 樹,當 RenderObject 擁有同一個座標系(比方 canvas、absolute)時,它們會合併爲一個 RenderLayer,這一步由 CPU 負責合成。接着轉換爲 GraphicsLayer 樹,當 RenderLayer 知足合成層條件(比方 transform,熟知的硬件加速)時,會有本身的 GraphicsLayer,不然與父節點合併,這一步一樣由 CPU 負責合成。最後,每一個 GraphicsLayer 都有一個 GraphicsContext 對象,負責將層繪製成位圖做爲紋理上傳給 GPU,由 GPU 負責合成多個紋理,最終顯示在屏幕上。 另外,爲了提高渲染性能效率,瀏覽器會有專使用的 Compositor 線程來負責層合成,同時負責解決部分交互事件(比方滾動、觸摸),直接響應 UI 升級而不阻塞主線程。主線程把 RenderLayer 樹同步給 Compositor 線程,由它開啓多個 Rasterizer 線程,進行光柵化解決,在可視區域以瓦片爲單位把頂點數據轉換爲片元,最後交付給 GPU 進行最終合成渲染。
首先要將JavaScript分紅同步任務和異步任務,其次是引入宏任務(macro-task)和微任務(micro-task)
下面用一副導圖來描述一下同步任務與異步任務的執行
task->渲染->task->...
)console.log(1);
setTimeout(function(){
console.log(2);
},1000);
console.log(3);
var p = new Promise(function(resolve,reject){
setTimeout(function(){
console.log(4);
},500);
resolve();
}).then(function(){
console.log(5);
});
console.log(6);
p.then(function(){
console.log(7);
});
console.log(8)
// 1 3 6 8 5 7 4 2
複製代碼
第一次宏任務循環: 執行的整段代碼看作一個宏任務
console.log(1)
打印1setTimeout()
交給定時器處理線程處理,1秒延時後將任務加入宏任務隊列 記爲 s1console.log(3)
打印3Promise
,執行裏面的回調函數setTimeout()
交給定時器處理線程處理,0.5秒延時後將任務加入宏任務隊列 記爲s2then
中的回調函數加入此次的微任務隊列 then1
Promise
後,執行console.log(6)
,打印6p.then
,把回調函數加入微任務隊列 then2
console.log(8)
打印 8 此時打印的是 1 3 6 8
宏任務隊列:s1
,s2
微任務隊列:then1
,then2
第一次宏任務執行結束以後執行此次產生的微任務,依次執行then1
,then2
,依次打印 5
和8
第二次宏任務開始 這裏須要注意的是s1
和s2
觸發計時事件,是在Event Table
中註冊,等待計時完畢以後把回調函數加入Event Queue
中,因此 s2
的0.5
秒比s1
的1
秒要先完成,這樣在Event Queue
中先放入s2
,再放入s1
第二次宏任務執行s2
,打印4
,沒有微任務 第三次宏任務執行s1
,打印2
,沒有微任務
一些定時事件、點擊事件、網絡加載都是交給瀏覽器的API處理的,這些地方會建立宏任務,並且並非直接加入到隊列中,而是有一個EventTable來記錄這些事件,等到條件知足以後再將回調的函數註冊到Event Queue中,每次宏任務從 Event Queue中獲取下一個宏任務