(本片文章若是你能耐着性子看我,保證會對同步和異步有一個很是深入的理解)瀏覽器
JavaScript是單線程執行,所謂的單線程呢就是指若是有多個任務就必須去排隊,前面任務執行完成後,後面任務再執行。由於JavaScript是一門單線程語言,因此咱們能夠得出結論:
JavaScript是按照語句出現的順序執行的多線程
若是在函數返回結果的時候,調用者可以拿到預期的結果(就是函數計算的結果),那麼這個函數就是同步的.
console.log('hello');//執行後,得到了返回結果
若是函數是同步的,即便調用函數執行任務比較耗時,也會一致等待直到獲得執行結果。(因此在JavaScript中對於耗時的操做或者時間不肯定的操做,使用異步就成了必然的選擇。)以下面的代碼:異步
function wait(){ for(var i=0;i<9999999999;i++){ if (i==9999999999){ console.log('i須要慢慢的加1直到99999999999999'); return } } } wait(); console.log('要等到i增長到9999999999(也就是wait()函數執行完畢)才能打印出這句話,大概要等10秒左右');
上面代碼中,函數wait是一個耗時程序,持續10秒左右,在它執行的這漫長的10秒中,下面的console.log()函數只能等待,這就是同步。因此在JavaScript中對於耗時的操做或者時間不肯定的操做,使用異步就成了必然的選擇。ide
若是在函數返回的時候,調用者還不可以獲得預期結果,而是未來經過必定的手段獲得(例如回調函數),這就是異步。例如處理AJAX請求的線程、處理DOM事件的線程、定時器線程、讀寫文件的線程等等。 若是函數是異步的,發出調用以後,立刻返回,可是不會立刻返回預期結果。調用者沒必要主動等待,當被調用者獲得結果以後會經過回調函數主動通知調用者。
function wait(){//下面每句打印的話前面的數字是她們在控制檯打印出來以後的前後順序 console.log('1:wait()函數開始執行了'); var time1 = (new Date()).getTime();//獲取當前的unix時間戳 setTimeout(function () { var time2=new Date().getTime(); while((time2-time1 > 5000)){ console.log('4:5秒過去了'); return } },5000)//等5秒而後獲取當前的unix時間戳 console.log('2:wait()函數執行結束了'); } wait(); console.log('3:雖然js會先執行上面的wait函數,而且裏面有個5秒的定時器,可是定時器函數是異步的,意思就是說wait()函數裏面的定時器啓動,並開始計時的時候並不影響繼續執行定時器以後的代碼');
上面的代碼的打印結果:函數
例二:spa
//打印出什麼 3 3 3 for(var i=0;i<3;i++){ setTimeout(function(){//凡是異步操做,必定是在全部同步操做執行完畢以後才能執行,也就是for循環執行完畢以後才能執行定時器裏面的代碼,此時i=3了 console.log(i); },100) }
瞭解完同步和異步以後,咱們再來看看咱們的問題:單線程又怎麼會有異步呢?
JavaScript其實就是一門語言,說是單線程仍是多線程得結合具體運行環境。衆所周知,js的運行環境就是瀏覽器,具體由js引擎取解析和執行。下面咱們來了解下瀏覽器。
一個瀏覽器一般由如下幾個常駐的線程:線程
要注意的是渲染引擎和js引擎線程是不能同時進行的。渲染線程在執行任務的時候,js引擎線程會被掛起。由於如果在渲染頁面的時候,js處理了DOM,瀏覽器就不知道該聽誰的了
一般講到瀏覽器的時候,咱們會說到兩個引擎:渲染引擎和JS引擎。
一、渲染引擎:Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trdent引擎,FireFox用的是Gecko引擎。不一樣的引擎對同一個樣式的實現不一致,就致使瀏覽器的兼容性問題。
二、JS引擎:js引擎能夠說是js虛擬機,負責解析js代碼的解析和執行。一般有如下步驟:
不一樣瀏覽器的js引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。之因此說js是單線程就是由於瀏覽器運行時只開啓一個js解釋器,緣由是如有兩個線程操做DOM,瀏覽器就又暈了。unix
JavaScript是單線程的,可是瀏覽器不是單線程的。一些I/O操做,定時器的計時和事件監聽是由其餘線程完成的。code
由上面瀏覽器一篇的介紹能夠知道,瀏覽器中多個線程的合做完成了異步的操做,那麼異步的回調函數又是怎樣完成執行的呢?
![]()
這就須要瞭解消息隊列和事件循環了。對象
如上圖所示,左邊的棧存儲的是同步任務,就是那些能當即執行、不耗時的任務,如變量和函數的初始化、事件的綁定等等那些不須要回調函數的操做均可歸爲這一類。右邊的堆用來存儲聲明的變量、對象。下面的隊列就是消息隊列,一旦某個異步任務有了響應就會被推入隊列中。如用戶的點擊事件、瀏覽器收到服務的響應和setTimeout中待執行的事件,每一個異步任務都和回調函數相關聯。
JS引擎線程用來執行棧中的同步任務,當全部同步任務執行完畢後,棧被清空,而後讀取消息隊列中的一個待處理任務,並把相關回調函數壓入棧中,單線程開始執行新的同步任務。
JS引擎線程從消息隊列中讀取任務是不斷循環的,每次棧被清空後,都會在消息隊列中讀取新的任務,若是沒有新的任務,就會等待,直到有新的任務,這就叫事件循環。
上圖以AJAX異步請求爲例,發起異步任務後,由AJAX線程執行耗時的異步操做,而JS引擎線程繼續執行堆中的其餘同步任務,直到堆中的全部異步任務執行完畢。而後,從消息隊列中依次按照順序取出消息做爲一個同步任務在JS引擎線程中執行,那麼AJAX的回調函數就會在某一時刻被調用執行。