前言
-
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一變化,界面就立刻重新渲染,效率必然很低,所以瀏覽器的機制規定界面渲染線程和主線程是互斥的,主線程執行任務時,瀏覽器渲染線程處於掛起狀態。