前端之JS的線程(最易懂)

1. 前言

不少文章在介紹線程以及線程之間的關係,都存在着脫節的現象。還有的文章過於廣大,涉及到了內核,本文但願以通俗易懂的話去描述晦澀的詞語,可能會和實際有一丟丟的出入,可是更易理解。html

咱們都知道JS是單線程的,即js的代碼只能在一個線程上運行,也就說,js同時只能執行一個js任務,可是爲何要這樣呢?這與瀏覽器的用途有關,JS的主要用途是與用戶互動和操做DOM。設想一段JS代碼,分發到兩個並行互不相關的線程上運行,一個線程在DOM上添加內容,另外一個線程在刪除DOM,那麼會發生什麼?以哪一個爲準?因此爲了不復雜性,JS從一開始就是單線程的,之後也不會變。node

這裏咱們已經知道了,一段JS代碼只能在一個線程從上到下的執行,可是咱們遇到setTimeout或者ajax異步時,也沒有等待啊,往下看。ajax

2. 瀏覽器

既然JS是單線程的,那麼諸如onclick回調,setTimeout,Ajax這些都是怎麼實現的呢?是由於瀏覽器或node(宿主環境)是多線程的,即瀏覽器搞了幾個其餘線程去輔助JS線程的運行。瀏覽器

瀏覽器有不少線程,例如:安全

  1. GUI 渲染線程bash

  2. JS 引擎線程多線程

  3. 定時器觸發線程 (setTimeout)dom

  4. 瀏覽器事件線程 (onclick)異步

  5. http 異步線程函數

  6. EventLoop輪詢處理線程

    ...

其中,一、二、4爲常駐線程

接下來,咱們對這些線程進行分類。

3. 線程與進程

什麼是進程?

咱們能夠在電腦的任務管理器中查看到正在運行的進程,能夠認爲一個進程就是在運行一個程序,好比用瀏覽器打開一個網頁,這就是開啓了一個進程。可是好比打開3個網頁,那麼就開啓了3個進程,咱們這裏只研究打開一個網頁即一個進程。

一個進程的運行,固然須要不少個線程互相配合,好比打開QQ的這個進程,可能同時有接收消息線程、傳輸文件線程、檢測安全線程......因此一個網頁可以正常的運行並和用戶交互,也須要不少個進程之間相互配合,而其主要的一些線程,剛纔在上面已經列出來了,分類:

類別A:GUI 渲染線程

類別B:JS 引擎線程

類別C:EventLoop輪詢處理線程

類別D:其餘線程,有 定時器觸發線程 (setTimeout)、http 異步線程、瀏覽器事件線程 (onclick)等等。

注意: 類別A和類別B是互斥的,緣由不用說了,不知道的看我上一篇文章。因此咱們下面的討論,就不涉及類別A了,只討論類別B、C、D之間的關係。

類別B:

JS 引擎線程,咱們把它稱爲主線程,它是幹嗎的?即運行JS代碼的那個線程(不包括異步的那些代碼),好比:

1 var a = 2;
2 setTimeout()
3 ajax()
4 console.log()
複製代碼

第一、4行代碼是同步代碼,直接在主線程中運行;第二、3行代碼交給其餘線程運行。

主線程運行JS代碼時,會生成個執行棧,能夠處理函數的嵌套,經過出棧進棧這樣,這裏不作過多介紹,不少文章。

消息隊列(任務隊列)

能夠理解爲一個靜態的隊列存儲結構,非線程,只作存儲,裏面存的是一堆異步成功後的回調函數字符串,確定是先成功的異步的回調函數在隊列的前面,後成功的在後面。

注意:是異步成功後,才把其回調函數扔進隊列中,而不是一開始就把全部異步的回調函數扔進隊列。好比setTimeout 3秒後執行一個函數,那麼這個函數是在3秒後才進隊列的。

類別D:

定時器觸發線程 (setTimeout)、http 異步線程、瀏覽器事件線程 (onclick)

主線程執行JS代碼時,碰到異步代碼,就把它丟給各自相對應的線程去執行,好比:

1 var a = 2;
2 setTimeout(fun A)
3 ajax(fun B)
4 console.log()
5 dom.onclick(func C)
複製代碼

主線程在運行這段代碼時,碰到2 setTimeout(fun A),把這行代碼交給定時器觸發線程去執行

碰到3 ajax(fun B),把這行代碼交給http 異步線程去執行

碰到5 dom.onclick(func C) ,把這行代碼交給瀏覽器事件線程去執行

注意: 這幾個異步代碼的回調函數fun A,fun B,fun C,各自的線程都會保存着的,由於須要在將來的某個時候,將回調函數交給主線程去執行啊。。。

因此,這幾個線程主要幹兩件事:

  1. 執行主線程扔過來的異步代碼,並執行代碼
  2. 保存回調函數,在將來的某個時刻,通知EventLoop輪詢處理線程過來取相應的回調函數而後執行(下面會講)

類別C:

EventLoop輪詢處理線程

上面咱們已經知道了,有3個東西

  1. 主線程,處理同步代碼
  2. 類別D的幾個異步線程,處理異步代碼
  3. 消息隊列,存儲着異步成功後的回調函數,一個靜態存儲結構

這裏再對消息隊列說一下,其做用就是存放着將來要執行的回調函數,好比

setTimeout(() => {
    console.log(1)
}, 2000)
setTimeout(() => {
    console.log(2)
}, 3000)
複製代碼

在一開始,消息隊列是空的,在2秒後,一個 () => { console.log(1) } 的函數進入隊列,在3秒後,一個 () => { console.log(2) }的函數進入隊列,此時隊列裏有兩個元素,主線程從隊列頭中挨個取出並執行。

到這裏咱們就知道了,這3個東西大概的做用、關係和流程,可是,它們3個互相怎麼交流的?這須要一箇中介去專門去溝通它們3個,而這個中介,就是EventLoop輪詢處理線程

既然叫輪詢了,那麼確定是不斷的循環的去交流和溝通

圖畫的有點醜,可是大概是這個意思,從主線程那裏順時針的看。

注意整個的流程是循環往復的。

注意只有主線程的同步代碼都執行完了,纔會去隊列裏看看還有啥要執行的沒

小區別

在異步線程類別D那裏,還有一些小區別:

主線程把setTimeout、ajax、dom.onclick分別給三個線程,他們之間有些不一樣

  • 對於setTimeout代碼,定時器觸發線程在接收到代碼時就開始計時,時間到了將回調函數扔進隊列

  • 對於ajax代碼,http 異步線程當即發起http請求,請求成功後將回調函數扔進隊列

  • 對於dom.onclick,瀏覽器事件線程會先監聽dom,直到dom被點擊了,纔將回調函數扔進隊列

4. 整體實例

var a = 111;


setTimeout(function() {
    console.log(222)
}, 2000)

fetch(url)  // 假設該http請求花了3秒鐘
.then(function() {
    console.log(333)
})

dom.onclick = function() {  // 假設用戶在4秒鐘時點擊了dom
    console.log(444)
}


console.log(555)


// 結果
555
222
333
444

複製代碼

步驟1:

主線程只執行了var a = 111;和console.log(555)兩行代碼,其餘的代碼分別交給了其餘三個線程,由於其餘線程須要二、三、4秒鐘才成功並回調,因此在2秒以前,主線程一直在空閒,不斷的探查隊列是否不爲空。

此時主線程裏其實已是空的了(由於執行完那兩行代碼了)

步驟2:

2秒鐘以後,setTimeout成功了

步驟3:

步驟4:

注意

圖裏的隊列裏都只有一個回調函數,實際上有不少個回調函數,若是主線程裏執行的代碼複雜須要很長時間,這時隊列裏的函數們就排着,等着主線程啥時執行完,再來隊列裏取

因此從這裏能看出來,對於setTimeout,setInterval的定時,不必定徹底按照設想的時間的,由於主線程裏的代碼可能複雜到執行好久,因此會發生你定時3秒後執行,其實是3.5秒後執行(主線程花費了0.5秒)

以後我會再寫如何解決定時偏差的內容。。。

借兩個經典的圖

setTimeout、setInterval

關於這兩個的延遲和解決辦法,看這篇文章,也是常常考的一個知識點!!!

關於setInterval()你所不知道的地方

~~

最後:如有錯誤之處,還請見諒,提出後會立刻修改~~~

轉載請註明出處,謝謝~~

相關文章
相關標籤/搜索