瀏覽器組成、線程及event loop

瀏覽器組成

User interface: a. Every part of the browser display, except the window. b. The address bar, back/forward button, bookmarking menu, etc.javascript

Browser Engine: marshals actions between the UI and the rendering engine. This provides a high-level interface to the Rendering Engine. The Browser Engine provides methods to initiate the loading of a URL and other high-level browsing actions (reload, back, forward). The Browser Engine also provides the User interface with various messages relating to error messages and loading progress.html

Rendering engine(渲染引擎/內核) : a. Parse HTML and CSS. b. Display parsed content on the screen.java

JavaScript interpreter: Used to parse and execute JavaScript code.node

瀏覽器的線程

瀏覽器是多線程的,它們在內核制控下相互配合以保持同步。一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。web

1) javascript引擎是基於事件驅動單線程執行的(能夠修改DOM,簡單化處理了),必須符合ECMAScript規範。JS引擎一直等待着event loop中任務的到來,而後加以處理(只有當前函數執行棧執行完畢,纔會去任務隊列中取任務執行)。瀏覽器不管何時都只有一個JS線程在運行JS程序。ajax

2) UI渲染線程負責渲染瀏覽器界面,當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時,該線程就會執行。可是 GUI渲染線程與JS引擎是互斥的,當JS引擎執行時GUI線程會被掛起,JS對頁面的操做即GUI的更新也會被保存在一個隊列中,等到JS引擎空閒時纔有機會被執行。這就是JS阻塞頁面加載。chrome

3) 事件觸發線程,當一個事件被觸發時該線程會把事件添加到任務隊列的隊尾,等待JS引擎的處理。這些事件能夠來自JavaScript引擎當前執行的代碼塊調用setTimeout/ajax添加一個任務,也能夠來自瀏覽器其餘線程如鼠標點擊添加的任務。但因爲JS的單線程關係,全部這些事件都得排隊等待JS引擎處理。api

 "任務隊列"是一個事件的隊列(也能夠理解成消息隊列),當瀏覽器的其餘線程完觸發一項任務時,就在js引擎的"任務隊列"中添加一個事件/任務,等js引擎的函數執行棧空閒時就會讀取任務隊列的事件,並執行該事件對應的回調函數。隊列中後面的任務必須等前面的任務執行完才能被執行。該過程會一直循環不停,以下圖所示:promise

Event loops

HTML規範中有要求瀏覽器實現。簡單總結以下:
爲了協調事件,用戶接口,腳本,渲染,網絡等,瀏覽器必須使用event loops。有對 browsing contexts和 workers的兩種類型。An event loop has one or more task queues.來自同一個task source的全部tasks必須放入同一個task queue。一般有4中task source:DOM manipulation task source,user interaction task source,networking task source和history traversal task source。同一個task queue是按先進先出順序執行的。瀏覽器能夠根據task queue中task source的不一樣,給予task queue不一樣的優先級。好比爲了及時響應,the user agent could then give keyboard and mouse events preference over other tasks three quarters of the time。即由瀏覽器決定挑哪個task queue中的隊首task執行。
另外Each event loop has a microtask queue. Microtask execute in order, and are executed:瀏覽器

  • after every callback, as long as no other JavaScript is mid-execution;
  • at the end of each task.

task分爲兩類:
macro-task:script( js codes as a whole in script tag),setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering;
micro-task:process.nextTick, Promises, Object.observe, MutationObserver.

js引擎會把task push到對應的macrotask queue(即task queue)或microtask queue中。在event loop的一個回合中,會先從macrotask queue中取出隊首的任務進行執行;執行完畢後,再依次執行microtask queue中的全部任務;若是在執行過程當中添加了新的microtask,則會在當前的回合中繼續執行,直到所有mircotask執行完畢才進入下一個event loop回合。

An event loop must continually run through the following steps for as long as it exists:

  1. select the oldest task(task A) in task queues.If task A is null(means task queues is empty),jump to step 5(microtasks steps)
  2. set "currently running task" to "task A"
  3. run "task A"(means run the callback function)
  4. set "currently running task" back to null,and remove "task A" from its task queue
  5. perform microtask queue
    • (a).select the oldest task(task x) in microtask queue
    • (b).if task x is null(means microtask queues is empty),jump to step (g)
    • (c).set "currently running task" to "task x"
    • (d).run "task x"
    • (e).set "currently running task" to null,remove "task x" from the microtask queue
    • (f).select next oldest task in microtask queue,jump to step(b)
    • (g).finish microtask queue;
  6. update rendering
  7. next routnd:jump to step 1

microtask執行時間過長會致使macotask任務的阻塞,致使UI渲染的阻塞。nodejs的process.nextTick限制1000個tick任務,以使macrotask得以執行。

案例解析1: 

//promises come from ECMAScript rather than HTML
setTimeout(function(){console.log(4)},0); new Promise(function(resolve){ console.log(1) for( var i=0 ; i<10000 ; i++ ){ i==9999 && resolve() } console.log(2) }).then(function(){ console.log(5) })then(function(){ console.log(6) }); console.log(3); //整個js代碼是script task,屬於macrotask,會在當前task queue中執行,在執行過程當中碰到setTimeout,會push到task queue中,但要等下一回合執行;第一個then()屬於microtask,在本回合執行,返回undefined,第二個then()也會在當前回合執行

 案例解析2:js線程一直執行,stack就不爲空,瀏覽器就沒有機會取task queue中的UI render任務更新頁面。chrome dev tools的斷點調試會影響event loop實時更新UI.

Render queue:瀏覽器在1s中渲染頁面60次,每16ms就會往Render queue中添加一個UI render任務。可是瀏覽器只有在stack爲空時纔有機會執行該任務。經過setTimeout(cb,0)將任務分割,就是增長UI render 任務被執行的機會,改善用戶體驗。

能夠將計算量大的任務經過setTimeout分割成多個小任務,這樣瀏覽器就有時間執行UI線程。可是很差的地方是計算所要花的時間會更長。分析performance,想辦法改善代碼(單次小任務中執行更多的計算)以下降cpu idle的佔比。

事件冒泡再次執行的回調仍屬於同一個task:

經過js代碼觸發click事件(targetEle.click())會致使在執行回調的間隙仍在script task中,故stack不會爲空,阻塞microtask執行。

相關文章
相關標籤/搜索