天天看點

至簡·js運作機制

快有一個月沒寫部落格了,真是造孽啊。雙十一也過去了,然鵝什麼都沒買。雙十一的結束,也預示着我長達十一個月的單身生活結束了,又要開始新的單身生活。最近這段時間一直在回顧反思,并學習一些新的東西。對于js的運作機制,也有了更深入的了解,之前寫過一篇文章,但是那篇文章對于js的底層原理并沒有深入的探讨。

通過這幾個禮拜的學習,我重新去深入的了解底層的運作機制,在此作以分享。

先上圖:

精髓之圖!!!

至簡·js運作機制

這張圖看着“比較”複雜,也許你已經不想看了,随你便,我無所謂,emmmm……反正我會對其中的部分進行詳細的講解,

我會從以下幾個部分對這張圖的内容進行解釋。

(1)程序與線程

(2)多程序的浏覽器

(3)為什麼js單線程

(4)同步與異步

(5)事件清單,事件隊列,ESC執行棧,EventLoop。

(6)其他部分

(7)Promise中js處理機制的進階宏任務與微任務。

對于這篇文章目前的規劃就是這些。接下來就來聊聊這些。

(1)程序與線程

為什麼要說這個,對于大多數人也許了解什麼是程序什麼是線程,即便如此還是有必要說一說的,并且很多人對于浏覽器是類型的卻并不了解,是以有必要說一下。

程序是記憶體配置設定的最小機關

線程是cpu排程的最小機關

抽象派:

程序就是一個加工廠,每個加工廠裡的勞工相當于線程,勞工們互相協作完成任務,工廠内部的資源大家共同利用。工廠與工廠之間互相獨立。

每個工廠裡,都至少有一個勞工。這就是程序與線程的原理。不做深究。

(2)多程序的浏覽器

浏覽器是多程序的,可以通過檢視浏覽器的任務管理器了解,如下圖:

至簡·js運作機制

我們可以看到,浏覽器是多程序的,并且每打開一個新的tab頁,就會增加一個程序。

并且相同的tab頁的程序會被合并,這些可以自行驗證。

浏覽的對程序主要有以下幾個:

  • Browser程序
  • render程序
  • GPU程序
  • 第三方插件程序
  • 以及每個tab頁都是一個程序

(3)js單線程

我們所接觸的每個頁面是一個程序,而參與處理這個頁面的程序主要是由render程序完成的,render程序内部又是多線程的,主要分為以下幾個線程

  • 異步的http請求線程;
  • 事件觸發線程
  • 定時器觸發線程
  • GUI渲染線程
  • js引擎線程

從上面可以看出來,浏覽器隻給了每個頁面處理js的一條線程,也就是js引擎線程,是以js是單線程的,這大概衆所周知了,可是為什麼js是單線程的,不見得重做周知,浏覽器是多程序的,對于每個頁面的處理其實也是多線程的,但是對于js的處理,浏覽器就給了一條線程。為什麼呢,了解一樣技術首先得從它的需求入手,不是說什麼什麼也可以什麼什麼,事情并沒有想的那麼簡單。對于js也不例外,js的設計初衷,是為了頁面互動,為了給浏覽網頁的使用者帶來更良好的互動體驗,試想,如果js是多線程的,在同一時刻使用者操作網頁中的某個節點,但同時另一個線程要删除這個節點,浏覽器該當如何,既不能影響使用者正常體驗,又要删除,并且誰能知道這個節點操作後,是否還會有後續操作。會帶來一系列的問題。是以作者,反其道而行之,不如化簡為一,隻通過一條線程去處理。這就是為什麼js最初被設計成單線程的原因。當然省了事,就會帶來别的問題。

(4)同步與異步

同步通俗的說就是一個時間段隻能幹一件事,每個任務都必須在前一個任務執行結束後,在執行,就如同排隊一樣,來的早的先辦事,并且前一個任務不論耗時多久,後面的任務都得等,知道前面的任務結束。這樣就會造成一些問題,例如:如果一個頁面在執行某個任務,但是是以耗費了很長的執行時間,導緻的問題就是,使用者不能進行别的操作,一直等待頁面的響應,這就會産生一種頁面卡死的現象,會帶來極差的使用者體驗。是以有了異步的産生。

異步是指:通俗的說就是同時幹多件事,不必等待之前的任務執行結束,後面的任務也可以去執行,這樣就可以解決同步所造成的資源浪費,并且對于js來說,也就能更有效的利用cpu的資源,js本質是直接耗費cpu資源的,這也是解釋性語言的特點,直接通過解釋器,再通過解碼器,有的甚至不通過解碼器例如V8引擎,就直接轉換為二進制,供cpu進行運算。

但是js中是如何實作異步的呢,按理來說js不是單線程嗎?我們要明确一點js是單線程,本質上并沒有改變。隻是它實作異步的方式有它的不同之處。

(5)事件清單,事件隊列,ESC執行棧,EventLoop。

接下來就是重頭戲了。這裡開始涉及到js的運作機制問題,是以敲黑闆!!!

ESC執行棧:Execution Context即管理執行期上下文,是一個棧結構,所有的同步任務均由他管理執行,并且同步任務均屬于js主線程上的任務,由ESC執行棧完成調用,所有的其他的異步任務基本上存在于事件清單中,事件清單與事件隊列并不相同,異步任務主要分兩種,一種是定時器異步任務,另一種是回調任務,這兩種任務分别又不同的線程控制,定時器類型的異步任務是由定時器觸發線程管理,異步回調任務是由事件處理線程進行管理的,接下來說說這些任務的運作方式。

:事件清單,存放所有的異步任務,在事件清單中挂起執行,

:事件隊列,異步任務執行得到響應後,會将對應的完成的異步任務事件加至事件隊列中,之後被ESC執行棧調用。

整個的這一過程就是EventLoop的機制。

對于主線程上的任務,他們會進入ESC執行棧,受執行棧直接控制,然後對應的異步任務,會被對應的線程進行管理,回調類型的異步任務會進入事件清單,此類任務會被挂起執行,等待I/O傳回結果,結果傳回後,會被推入事件清單,等待ESC執行棧中的任務執行完畢,然後進行EventLoop(事件循環),會到事件隊列中的頭部擷取等待執行的任務。對于定時器類型的異步任務,直接由定時器事件觸發線程管理,定時到時後就會将任務直接推入事件隊列之中,如此循環往複,隻要ESC執行棧中的任務結束,也就是主線程上的任務結束,就會進行事件循環,檢查事件清單,異步任務,通過各自線程的管理進入事件清單,在沒有主線程任務時被ESC擷取,這就是最初的js執行機制。

對于執行棧部分的js代碼解析過程,在本章節不重點讨論。但是大概說一下,因為涉及到的知識點比較多如詞法作用域、scope、scope chain、prototype、prototype chain、VO、AO、以及Context(執行期上下文)、解析規則等等的問題,可以重新開幾個章節讨論了,是以我隻提幾個點,

像之前說的任務進入ESC執行棧中執行,說白了就是function裡的代碼在ESC執行棧裡執行,但是在進入ESC執行棧執行之前還會有好多的事情發生,在函數建立的時候,就會發生一件事,即函數的作用域在它定義時就決定了,函數内部的屬性[[scope]]會儲存所有函數外部變量到其中,就是所有外部變量對象的層級連,然後在函數進入ESC時函數被激活,别以為就這麼簡單,在激活的時候,也會發生很多事,建立作用域、建立活動對象AO,并且初始化AO,加入形參,函數聲明,變量聲明,然後會就将AO對象壓入自身作用域鍊的頂端,然後才開始操作函數,函數執行的時候就是對AO對象進行修改,需要什麼先在自己的AO上找,找不到就沿着作用鍊向上找。最後函數執行結束,從ESC中彈出,被釋放。大緻就是如此。

進階:

以上所講的js運作機制,很多人已經了解,當然很多人應該也不了解,當初我了解到此處的時候已經覺得上了一個層面,可是後來因為一些程式設計中遇到的例子,發現并不能解釋的通,就很奇怪,于是乎又是一對搜尋,不過說實話,搜尋能給的幫助太少了,多數文章千篇一律,一個抄一個,最後還是在基佬俱樂部github上得到了答案。

   之前的運作機制,在我們遇到一般的程式可以解釋的通,并且之前的運作機制也并沒有錯,隻是它還不夠完善。因為Promise的出現,它将js運作機制又劃分了另一個層面。可以嘗試一下圖檔中的例子,我都給到了,可以自己試一試。Promise将js底層的任務劃分的更加明确,即微任務(microTask)和宏任務(macroTask)的概念

   宏任務:就是ESC執行執行棧當中的任務,既包含目前的js主程式代碼,定時器的異步觸發任務等,因為最終都會進入ESC執行棧,他們是從事件隊列中擷取的。

   微任務:是更為細化的一種任務,它的執行比setTimeOut還要快,setTimeOut最小時間為4ms,即便設為0,這是最小的設定。例如Promise、Promise.nextTick這些是屬于微任務。

那他們是怎麼運作的呢?

   宏任務依然是在ESC執行棧中執行,與之前的同步任務,還有異步任務的運作方式是一樣的,并且事件的循環也是一樣的,隻是在每次重新向ESC執行棧中添加新任務時,會加一層操作,就是檢查微任務隊列。在代碼執行過程中,所有遇到的微任務microTask,也會進入一個隊列中,這個微任務隊列直接由js引擎線程控制。每次ESC執行棧中的一個宏任務執行結束,就會去檢查微任務隊列,尋找本輪是否還有微任務沒有執行,然後執行微任務。隻有本輪的宏任務和微任務都執行結束,然後開始檢查渲染,此時由GUI線程接管渲染。渲染完畢後,就又會由JS引擎接管,開始下一輪的宏任務。

   問題到了此處,已經又是一個層面了,是不是覺得我逼話連篇,嘿嘿嘿,但是,了解到此處,卻是豁然開朗的感覺,我也懶的敲代碼示例了,了解原理,并且總結畫出那張圖花了不少時間,并且也把代碼示例附在了圖上,可以自己嘗試。唉…,底層的東西太多,太深了,我還是知道的少。繼續吧,不要松懈。

哦對了,圖檔裡還涉及到一些,其他的知識,有興趣的可以自行了解,本篇就先說到這裡。有些點說的比較籠統,後續應該還會有補充。

繼續閱讀