JS異步執行機制


前言

  • JS異步執行機制具有非常重要的地位,尤其體現在回調函數和事件等方面。

  • 異步加載也叫非阻塞模式加載

  • 同步或非同步,表明着是否需要將整個流程按順序地完成

  • 阻塞或非阻塞,意味着你調用的函數會不會立刻告訴你結果

javascript的單線程和異步

  • js是單線程語言(能提高效率。作為瀏覽器腳本語言,js的主要用途是與用戶互動,操作DOM。而這也就決定它只能為單線程,否則會帶來很復雜的同步問題),瀏覽器只分配給js一個主線程,用來執行任務(函數),但一次只能執行一個任務,這些任務形成一個任務隊列排隊等候執行,但前端的某些任務是非常耗時的,比如網絡請求,定時器和事件監聽,如果讓他們和別的任務一樣,都老老實實的排隊等待執行的話,執行效率會非常的低,甚至導致頁面的假死。所以,瀏覽器為這些耗時任務開辟了另外的線程,主要包括http請求線程,瀏覽器定時觸發器,瀏覽器事件觸發線程,這些任務是異步的

  • js是單線程語言,但js的宿主環境(比如瀏覽器,Node)是多線程的,宿主環境通過某種方式(事件驅動,下文會講)使得js具備了異步的屬性。
    詳情見:https://www.cnblogs.com/woodyblog/p/6061671.html

  • 雖然js是單線程,但是我們可以將任務分成兩類

1.同步任務:需要執行的任務在主線程上排隊,依次執行
2.異步任務:沒有立馬執行但是需要被執行的任務,放在任務隊列(task queue,一個事件的隊列或者消息的隊列)里面。

Event Loop事件循環

打開網站的時候,網頁的渲染其實是一堆同步任務,比如頁面骨架和頁面元素的渲染,但是像圖片音樂等占用資源大耗時久的任務就是異步任務。
主線程:

  • 1.所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)
  • 2.主線程之外,還存在一個任務隊列(task queue),只要異步任務有了運行結果,就在“任務隊列”之中放置一個事件。
  • 3.一旦“執行棧”中的所有同步任務執行完畢,系統就會讀取“任務隊列”,看看里面有哪些事件。那些對應的異步任務,就結束等待狀態,進入執行棧開始被執行。

js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數。

  • 4.主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,形成event loop(事件循環)
    舉例:
    任務隊列里放的是ajax這類的任務,是交給瀏覽器發起HTTP請求去執行的,當有了返回結果就會在任務隊列中增加一個事件,表示該ajax請求已經返回了結果,任務隊列里的任務和js主線程是同時執行的。不影響js是單線程的這個結論,只能說瀏覽器還會提供接口來供js。
    圖片說明
    圖例:同步和異步任務分別進入不同的執行"場所",同步的進入主線程,異步的進入Event Table並注冊函數。 當指定的事情完成時,Event Table會將這個函數移入Event Queue。 主線程內的任務執行完畢為空,會去Event Queue讀取對應的函數,進入主線程執行。

setTimeout

在使用setTimeout的時候,經常會發現設定的時間與自己設定的時間有差異,貼段代碼看一下

setTimeout(() => {
    task();
},3000)
console.log('執行console');
// 執行console 
// task()

setTimeout是一個異步的所以會先執行console這個同步任務。但是,如果改成下面這段會發現執行時間遠遠超過預定的時間。

setTimeout(() => {
    task()
},3000)
sleep(10000000)

我們來看一下是怎么執行的:

  • task()進入到event table里面注冊計時
  • 然后主線程執行sleep函數,但是非常慢。計時任然在繼續
  • 3秒到了。task()進入event queue 但是主線程依舊沒有走完
  • 終於過了10000000ms之后主線程走完了,task()進入到主線程
    所以可以看出其真實的時間是遠遠大於3秒的

解釋幾個容易困惑的問題

  • 1. setTimeout(f1,0) ,f1是不是立刻執行?
    答案是不一定,要看主線程內的命令是否已經執行完了。這個任務會在主線程最早可得的空閑時間執行,就是主線程的任務執行結束之后立馬執行
console.log('先執行這里');
setTimeout(() => {
    console.log('執行啦')
},0);
// 先執行這里
// 執行啦

HTML5標准規定了setTimeout()的第二個參數的最小值(最短間隔),不得低於4毫秒,如果低於這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。另外,對於那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常不會立即執行,而是每16毫秒執行一次。

  • 2. Ajax請求是否異步??
    ajax請求內容的時候是異步的,當請求完成后,會觸發請求完成的事件,然后把回調函數放入callback queue,等到主線程執行該回調函數時還是單線程的。
  • 3. 界面渲染線程是單獨開辟的線程,是不是DOM一變化,界面就立刻重新渲染?
    如果DOM一變化,界面就立刻重新渲染,效率必然很低,所以瀏覽器的機制規定界面渲染線程和主線程是互斥的,主線程執行任務時,瀏覽器渲染線程處於掛起狀態。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM