提及javascript(如下簡稱js)這門語言,相信你們已經很是熟悉了,無論是前端開發仍是後端開發幾乎無時無刻都要跟它打交道。雖然說開發者天天幾乎都要操做js,可是你真的肯定你掌握了js的運行機制嗎!下面咱們就來聊聊這話題。javascript
上圖咱們能夠分爲兩部分:瀏覽器中的JS引擎
和運行環境Runtime
,那它們的區別是什麼?前端
如上圖中能夠看出JS引擎分爲兩大核心部分:棧和堆
java
棧(Stack):js代碼的執行都要壓到此棧中執行。 ajax
堆:存放對象、數組的地方,js垃圾回收就是檢查這裏。後端
JS引擎是單線程的,也就是說在一個時間段內,事情只能一件一件的按前後順序去作,第一件事沒作完就不能第二件事。那麼在js引擎中負責解釋和執行js代碼的線程只有一個,咱們能夠稱之爲主線程
。數組
固然瀏覽器的運行環境Runtime還提供一些其餘的線程,如定時器線程、ajax線程、事件線程、網絡請求和UI渲染的線程,爲了和js主線程分開,咱們這裏都統稱它們爲工做線程
。瀏覽器
因爲瀏覽器是多線程的,因此工做線程和js主線程均可以執行任務,線程間互不干擾。網絡
在JavaScript任務能夠分爲兩種:多線程
異步任務:在棧執行代碼的過程當中,如遇到異步函數,如setTimeout、異步Ajax、事件處理程序,會將這些異步代碼交給瀏覽器的工做線程來處理,咱們把這些任務稱之爲異步任務。異步任務是不進入主線程,而是進入任務隊列(queue task)。異步
什麼異步函數?
異步函數一般是由發起函數和回調函數構成的。如:
A(callback)
它們都是在主線程調用的,其中發起函數用來發起異步過程,回調函數用來處理結果。 如:`setTimeout(callback,1000)` setTimeout就是發起函數、callback就是回調函數。 如:異步的Ajax
var xhr = new new XMLHttpRequest(); xhr.onreadystatechange = callback; //callback爲回調函數 xhr.open('get',url,true); xhr.send(null); // send爲發起函數
能夠看出發起函數和回調函數也能夠是分離的。
既然同步任務是在主線程中執行的,那麼異步任務什麼時候執行?
答:是這樣的,一旦棧中同步任務執行完畢後,系統就會經過事件循環
機制讀取任務隊列中的任務一個個移到棧中去執行。
當主線程中的任務執行完畢後,會從任務隊列中獲取任務一個個的放在棧中執行去執行,這個過程是循環不斷的,因此整個的這種運行機制又稱爲事件循環。
在js中,代碼最終都是在棧中執行的,棧結構的特色是:先進後出,後進先出。
咱們來看下面代碼的運行結果:
function bar(){ console.log(1); foo(); } function foo(){ par(); console.log(3); } function par(){ setTimeout(function(){ console.log(2); },0); } bar();
運行的最終結果是:132。 爲何結果不是123呢?
下咱們來分析下代碼運行時入棧和出棧的過程。
首先當調用函數bar()
時,此函數就會先入棧,其內部的console.log(1)
也會隨之入棧執行。
執行完console.log(1)後,就要出棧,因而控制檯先打印出結果1,只剩下bar()在棧中。接着再執行函數bar內部的函數foo,因而函數foo也開心的入棧了。
執行函數foo的內部代碼,調用函數par()
,因而函數par()也要跟着入棧。
因爲函數par()內部執行遇到了異步函數setTimeout
,異步函數則會由瀏覽器的Runtime運行環境的工做線程來處理,等定時器設置的時間到達就會被放到任務隊列中,此時棧的同步任務繼續執行。
接着在執行par函數中的console.log(3)
,控制檯打印結果爲3 ,此時棧的代碼執行完畢後,會按照棧的特色進行
先進後出,後進先出順序進行出棧
。出棧順序:先函數par()-->後函數foo()-->最後函數bar。
最後只剩下異步任務,由主線程去獲取任務隊列中的任務放在棧中去執行。也能夠認爲棧中的同步代碼執行老是在讀取異步任務
以前執行。
最後執行setTimeout中的回調函數:結果控制檯輸出爲2。
setTimeout(function(){ console.log(2); },0);
因此代碼的最終運行結果爲132。
異步任務
以前執行