【Javascript】JS的異步操做,瀏覽器的多線程間的協做

遇到的問題,引起了思考javascript

        今天看了一個例子,強烈引起了我對於瀏覽器多線程之間的操做機制、同步與異步、回調函數的興致,代碼以下:html

 

<html>
<head>
<title>title</title>
</head>
<body>
	<input type="text" value="" name="input" onkeydown="console.log(this.value)">
	<input type="text" value="" name="input" 
           onkeydown="var me = this;setTimeout(function(){console.log(me.value)});">
</body>
</html>

 

  若是有興趣,你能夠直接運行這段代碼,打開網頁,用F12進行調試,發現,代碼大同小異,可是控制檯輸出卻不同!!前端

  經過上面的現象,你會問,這是爲何???百思不得其解,給點耐心,往下看,涉及的知識點豐富着呢,哈哈。java

  這個例子能夠說很是罕見,本質緣由是瀏覽器的事件監聽線程與UI渲染線程發生衝突了,到底誰先於誰執行的問題。ajax

  若是沒有耐心,下面的第七點直接揭露真相。瀏覽器

 

1、前言網絡

        通過查閱了不少的資料,基本瞭解了JS的同步與異步的操做,瀏覽器內核的多線程併發操做,其中涉及到的知識點以下:多線程

    • 什麼是同步和異步?
    • JS的是基於事件驅動的單線程語言,爲啥會有異步操做這種多線程的操做???
    • 瀏覽器線程,瀏覽器內核線程間的合做?
    • JS的異步操做都有哪些?它是如何工做的?
    • AJAX請求的時候,瀏覽器內核線程機制是怎樣的工做的?
    • 當js主線程空閒時,此時同事發生事件觸發線程UI渲染線程,併發生衝突時,哪一個會先執行問題(例如表單中文本框的輸入與事件觸發)

 

2、js單線程併發

        單線程的含義是js只能在一個線程上運行,也就是說,同一時間只能作一件事情,其餘的任務則會放在任務隊列裏面排隊等等js線程處理。dom

        可是值得注意的是,雖然js是單線程語言,可是並不表明瀏覽器內核中的js引擎線程只有一個。js引擎有多個線程,一個主線程,其餘的線程配合主線程工做

 

3、爲何js選擇單線程

        與它的用途有關。做爲瀏覽器腳本語言,Javascript主要用途是與用戶互動,以及操做dom。這決定了它只能是單線程,不然會帶來複雜的同步問題。好比,假設javascript同事有兩個進程,一個線程在某個DOM階段添加內容,另外一個線程刪除了這個節點,這是瀏覽器應該以哪個線程爲準?想要實現這個問題,確定要加入線程鎖這個概念,那就複雜多了。

        單線程與異步確實不能同時成爲一個語言的特性,JS選擇成爲單線程語言,因此它自己是不可能異步的,可是js的宿主環境(好比瀏覽器,Node)是多線程的,宿主經過某種方式,使得js具有異步的屬性,若是你理解了,你會發現js的機制是多麼的簡單高效!

 

4、同步和異步

  • 同步:一次只能完成一件任務,若是有多個任務,必須排隊
  • 異步:每個任務有一個或者多個回調函數(callback),前一個任務執行後,把裏面回調函數拋出來,給瀏覽器的另一個線程處理。然後一個任務是不等待前一個任務結束就執行。
  • 一個例子來講明異步和同步:
<html>
<head>
    <title>異步與同步</title>
</head>
<body>
<script>
     setTimeout(function () {
          console.log("異步任務")
     })
     console.log("同步任務")
</script>
</body>
</html>

//輸出:
//同步任務
//異步任務

//執行順序:
//首先執行setTimeout,發現是個異步操做,就把這個異步操做丟給瀏覽器內核中的
//計時器線程,而後js主線程繼續執行console.log("同步任務"),以後js主線程空閒,
//從任務隊列裏面獲取異步操做任務,放到js主線程棧中執行,輸出「異步任務」

      

5、異步的好處

  1.異步的好處

        用一個同步與一個異步圖進行說明,假設有四個任務(一、二、三、4),它們各自的執行時間都是10ms,其中,任務2是任務3的前置任務,任務2須要20ms的響應時間。

 

  2.適合的場景

        能夠看出,當咱們的程序須要大量I/O操做和用戶請求時,js這個具有單線程,異步,事件驅動多種氣質的語言是多麼應景!相比於多線程語言,它沒必要耗費過多的系統開銷,同時也沒必要把精力用於處理多線程管理,相比於同步執行的語言,宿主環境的異步和事件驅動機制又讓它實現了非阻塞I/O,因此你應該知道它適合什麼樣的場景了吧!

 

6、JS的異步操做有哪些

        咱們已經知道,js一直是單線程執行的,瀏覽器爲了幾個明顯的耗時任務(例如ajax),單獨開闢線程解決耗時問題。可是JS除了這幾個明顯耗時問題外,可能咱們本身寫的程序裏面也會有耗時函數,怎麼解決?咱們確定不能直接開闢單獨的線程,可是咱們能夠利用瀏覽器給咱們開發的窗口(定時器線程事件觸發線程),總的來講,有一下4種異步操做

  • 定時器函數setTimeout(交給定時器線程處理)
  • 事件綁定(onclick、onkeydown....,交給事件觸發線程處理)
  • AJAX(交給HTTP線程處理)
  • 回調函數

 

7、說說瀏覽器的線程

        前面說了,js是單線程,瀏覽器只分配給JS一個主線程,用來執行函數,但一次只能執行一個任務,假設有多個任務,就會造成一個任務隊列,意味着這些任務須要排隊等待執行,但前端某些任務是很是耗時的,例如網絡請求、定時器和事件監聽,若是讓他們和別的任務同樣,老老實實排隊等待執行的話,執行效率會很是低,甚至可能致使頁面假死。

        因此爲了解決這個問題,瀏覽器爲這些耗時任務開闢了另外的線程,主要包括http請求線程,瀏覽器定時觸發器,瀏覽器事件觸發線程,這些任務都是異步的。依靠宿主(瀏覽器、Node)的多線程,來使本身具有異步的屬性。

 

        瀏覽器的內核是多線程,它們在內核控制下相互配合以保持同步,一個瀏覽器至少實現3個常駐線程

    • Javascript引擎線程(瀏覽器不管何時都只有一個JS線程在運行)
    • UI渲染線程(與Javascript引擎線程互斥,Javascript引擎線程執行時,UI渲染線程被掛起,當Javascript引擎空閒時,UI渲染線程當即執行)
    • 事件觸發線程(當一個事件被觸發時該線程會把事件添加到待處理隊列的對位,等待JS引擎處理。這些事件可來自Javascript引擎當前執行的代碼塊,如setTimeout,也能夠來自瀏覽器內核的其餘線程,如鼠標點擊、AJAX異步請求等。)

  下圖能夠說明瀏覽器的三個主要線程

        從圖片能夠看出,JS引擎線程是單線程,它從上到下執行任務,當執行到鼠標點擊事件、setTimeout這些異步任務的話,它會交給瀏覽器的對應的線程執行,事件觸發線程就會把回調函數插入到Javascript引擎線程的隊尾,等待js線程執行。這個過程當中,由於Javascript線程一直繁忙,因此UI渲染線程一直被掛起。

------------------------------------------------------------------------------------------------------------------------------------------------------

說到這裏,基本上能夠解決本文開篇所說的問題了。

第一個文本框的js是這樣寫的:

<input type="text" value="" name="input" onkeydown="console.log(this.value)">

  一開始按下鍵盤a的時候,已經觸發了事件,onkeydown爲異步操做,它的函數體會交給事件監聽進程,此時JS線程空閒,你覺得UI線程會啓動?事實上並不會,UI線程依然被掛起,緣由是事件監聽進程比UI線程更快執行(當事件觸發線程與ui渲染線程發生衝突時,例如例子中的表單的輸入與觸發,事件監聽進程把函數體插入到任務隊列尾部,JS引擎執行就經過event loop機制,把這段函數體壓進棧中執行。因JS引擎線程繁忙,因此UI渲染線程方面,a字符串無法渲染到dom裏,因此console.log在得到文本框的值時,爲」「(空)。以後js線程空閒,UI渲染線程執行,頁面渲染出a出來。

        第二個文本框的js是這樣寫的:

<input type="text" value="" name="input" onkeydown="var me = this;setTimeout(function(){console.log(me.value)},0);">

        一開始按下鍵盤a的時候,觸發了事件,js執行裏面的函數,發現setTimeout是異步操做時,雖然它的延遲設爲0,幾乎是便是觸發的,可是把函數丟給瀏覽器setTimeout定時器線程處理,此時js線程空閒,UI渲染線程把a渲染到頁面,以後事件觸發線程把回調函數插入js線程待處理的任務隊列,js線程執行回調函數,最後瀏覽器及時輸出字符串a。

 

8、最後說說js主線程

        js除了處理本身的主線程的任務以外,還在背地裏一直作一個工做,就是從任務隊列(callback queue)中提取任務,放到主線程裏執行,下圖深刻講解了JS主線程的工做:

        上圖的WebAPIs,能夠統一理解爲瀏覽器爲異步任務單獨開闢的線程

        上圖的callback queue,就是上面所說的任務隊列,裏面放的就是回調函數體

        JS主線程由堆(heap)棧(stack)共同組成。函數的執行經過進棧出棧來執行,例如foo()函數,主線程把它推動棧,在執行它的過程當中,發現還須要執行上面幾個函數,因此JS主線程又陸陸續續把幾個函數推動棧中,等到函數執行完,把他們一一推出棧,等到stack清空的時候,說明一個任務已經執行完成了,這時就會從callback queue中尋找下一個任務,把他推動棧中(這個尋找過程,就叫event loop,由於它老是循環查找任務隊列裏是否還有任務

 

9、說說一些應用與實踐吧

AJAX發送異步請求,瀏覽器作了什麼?

1.JS建立了一個ajax請求

2.瀏覽器另外打開一個ajax引擎線程,執行ajax請求

3.執行的到響應後將回調函數體放入任務隊列

4.js線程執行任務隊列中的回調函數

 

 

參考內容:

http://www.javashuo.com/article/p-pozgohqt-bd.html (博客主要參考)

https://zhuanlan.zhihu.com/p/23659122?refer=dreawer

http://www.javashuo.com/article/p-oxsktebw-gn.html

https://blog.csdn.net/baidu_24024601/article/details/51861792

https://www.bilibili.com/video/av18833649?from=search&seid=15859434788305704049 (視頻參考)

http://www.javashuo.com/article/p-xaannlnq-z.html

 

 

 

 

--END--

相關文章
相關標籤/搜索