1. 程式人生 > >與JavaScript非同步實現密切相關的瀏覽器核心執行緒

與JavaScript非同步實現密切相關的瀏覽器核心執行緒

JavaScript是單執行緒的,但是瀏覽器核心是多執行緒的。這個是JavaScript非同步實現的關鍵之處。

關於Ajax

JavaScript中關於Ajax的呼叫方式有兩種:同步和非同步。相信看到這篇文章的時候,你已經產生了這樣的疑問了:為什麼同步是阻塞UI的,為什麼非同步就不會阻塞UI了,非同步又是怎麼實現的呢?
那為什麼要區分這兩種情況呢?這和瀏覽器處理機制是密不可分的。JavaScript雖然是單執行緒的,但是瀏覽器核心是多執行緒的。在日常開發中儘管不會有執行緒間通訊,但實際上JS的宿主環境在後臺已經幫我們處理好了。JavaScript的非同步實現實際上就是執行緒間的通訊問題。

瀏覽器核心中的多執行緒

先來了解一下瀏覽器核心中都有哪些執行緒,以及這些執行緒是用來幹什麼的。

瀏覽器 GUI 渲染執行緒

面試中常常提及的一個問題就是頁面渲染的執行過程,這裡其實就是GUI執行緒在執行。

  • 負責渲染瀏覽器介面,包括解析HTML、CSS、構建DOM樹、Render樹、佈局與繪製等
  • 當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行

當瀏覽器中JavaScript引擎執行緒在執行過程中觸發UI渲染的時候,GUI執行緒此時是凍結狀態。也就是說對頁面結構的改動並不會立即渲染到頁面上,根本原因是GUI執行緒和JavaScript執行緒互斥。因為 JavaScript指令碼是可操縱DOM元素, 在修改這些元素屬性同時渲染介面, 那麼渲染執行緒前後獲得的元素資料就可能不一致了。因此瀏覽器決定將這兩個執行緒互斥處理。所以在指令碼中執行對介面進行更新操作, 如新增結點,刪除結點或改變結點的外觀等更新並不會立即體現出來, 這些操作將儲存在一個佇列中,待JavaScript引擎空閒時才有機會渲染出來。(JavaScript執行棧為空?)

可以點開console檢視下面程式碼的執行效果,對頁面的修改將會延遲處理。

var sleep = function(time) {
    var date = new Date(); 
    while(new Date() - date <= time) {}
}
document.body.innerHTML = '123';// reflow -- js執行過程中GUI處於凍結狀態,對頁面的修改不會立即執行,而是壓入GUI執行緒的事件佇列中等待執行。
sleep(3000)

所以當遇到在js引擎運算量較大的時候, 又想及時呼叫GUI渲染怎麼辦呢?參考下面的程式碼:

var
sleep = function(time) { var date = new Date(); while(new Date() - date <= time) {} } document.body.innerHTML = '123'; setTimeout(function() {//劃重點 -- 定時器是瀏覽器的另一個執行緒,所以這裡js的執行棧暫時是空的,也就是說不會影響到上一行程式碼觸發的GUI執行緒。 sleep(3000); }, 0);

JavaScript 引擎執行緒

  • JavaScript 事件驅動機制,事件迴圈和事件佇列。
  • 是JavaScript 程式碼的執行執行緒。
  • 所有在其他執行緒中等待壓入事件佇列的事件都將在這個執行緒中執行,也就是JavaScript中所謂的單執行緒處理機制。

瀏覽器定時觸發器執行緒

  • setInterval與setTimeout所線上程。
  • 定時計時器並不是由JS引擎計時的,因為如果JS引擎是單執行緒的,如果JS引擎處於堵塞狀態,那會影響到計時的準確。
  • 當計時完成被觸發,事件會被新增到事件佇列,等待JS引擎空閒了執行。而setInterval就是在一定的事件間隔內不斷的將這個事件壓入事件佇列。當setInterval中的事件處理時間大於延遲時間時會不斷重複觸發事件處理。
    注意:W3C的HTML標準中規定,setTimeout中低於4ms的時間間隔算為4ms。

瀏覽器事件觸發執行緒

  • 聽起來像JS的執行,但是其實歸屬於瀏覽器,而不是JS引擎,用來控制時間迴圈(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開執行緒協助)
  • 當JS引擎執行程式碼塊如setTimeout時(也可來自瀏覽器核心的其他執行緒,如滑鼠點選、AJAX非同步請求等),會將對應任務新增到事件執行緒中
  • 當對應的事件符合觸發條件被觸發時,該執行緒會把事件新增到待處理佇列的隊尾,等待JS引擎的處理
    注意:由於JS的單執行緒關係,所以這些待處理佇列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時才會去執行)

瀏覽器 http 非同步請求執行緒

  • 在XMLHttpRequest在設定async為true時連線後新啟動的一個執行緒
  • 執行緒如果檢測到請求的狀態變更,如果設定有回撥函式,該執行緒會把回撥函式新增到事件佇列。

Ajax同步更新UI

從上面的執行緒中來看,JavaScript中呼叫的Ajax實際上是啟動的另一個執行緒進行IO或者通訊等耗時操作,一旦設定成同步,當然作為JavaScript引擎執行緒自然也可以進行網路通訊,當耗時較長時,由於GUI執行緒處於凍結狀態,瀏覽器將暫時的失去響應陷入卡死狀態。
如果專案需求依舊是需要同步處理,那麼合理避免GUI執行緒和JavaScript執行緒的互斥的方法就可以是呼叫瀏覽器的定時執行緒了

setTimeOut(function(){
//ajax..
},0)//通過定時執行緒立即壓入js執行緒中的事件佇列

感謝巨人的肩膀: