異步與Event Loop

  JavaScript的單線程,與它的用途有關。作爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程爲準?所以,爲了避免複雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,將來也不會改變。
  單線程就意味着,所有任務需要排隊,前一個任務結束,纔會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等着。所以我們可以先掛起處於等待中的任務,先運行排在後面的任務。等到之前等待的有了結果,再回過頭,把掛起的任務繼續執行下去。
  於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入”任務隊列”(task queue)(事件隊列:I/O,鼠標點擊等)的任務,只有”任務隊列”通知主線程,某個異步任務可以執行了,該任務纔會進入主線程執行。
  具體來說,異步執行的運行機制如下。(同步執行也是如此,因爲它可以被視爲沒有異步任務的異步執行。)
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個”任務隊列”(task queue)。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
(3)一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”,看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
只要主線程空了,就會去讀取”任務隊列”,這就是JavaScript的運行機制。這個過程會不斷重複。
  所謂”回調函數”(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。

Event Loop

主線程從”任務隊列”中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱爲Event Loop(事件輪詢)。
  主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在”任務隊列”中加入各種事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取”任務隊列”,依次執行那些事件所對應的回調函數。
執行棧中的代碼(同步任務),總是在讀取”任務隊列”(異步任務)之前執行。
  除了放置異步任務的事件,”任務隊列”還可以放置定時事件,即指定某些代碼在多少時間之後執行。

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

上面代碼的執行結果是1,3,2,因爲setTimeout()將第二行推遲到1000毫秒之後執行。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

如果將setTimeout()的第二個參數設爲0,就表示當前代碼執行完(執行棧清空)以後,立即執行(0毫秒間隔)指定的回調函數。上面代碼的執行結果總是2,1,因爲只有在執行完第二行以後,系統纔會去執行”任務隊列”中的回調函數。

console.log('line 1')
setTimeout(console.log, 1000, 'line 2')
console.log('line 3')

以上一共三行代碼,該程序被執行的時候,會依次挨行執行

  • 第一步,執行第一行,將結果line 1打印出來
  • 第二步,執行第二行,注意此時會將這個操作暫時存儲到其他地方,因 爲setTimeout是一個異步執行操作。
  • 第三步,執行第三行,將結果line 3打印出出來
  • 第四步,等待最後一行程序(一共三行)都全部執行完了,然後立馬實時查看剛纔暫存的異步操作有沒有。如果有可執行的,就立即拿到出來繼續執行。
  • 第五步,執行完畢之後,再實時查看暫存位置中是否還有未執行的異步回調。

所以, setTimeout(function hello(),1000);這句代碼內部的運行順序:
  在js引擎執行完該行js代碼之後,會在1000毫秒之後會把hello()這個函數放入到事件隊列中。等到事件隊列中排在hello()之前的任務執行完畢後,hello()得到執行。所以setTimeout方法中設置的時間爲1000毫秒,只是說明在setTimeout()方法運行之後的1000毫秒時,把setTimeout方法的第一個參數:hello()函數放入到事件隊列中準備執行。注意,這裏是準備執行,而不是立即執行。所以,在setTimeout()方法執行後,到hello()執行的 時間差 總是大於1000毫秒的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章