最近本人對於js的運行機制,特別是異步,還有回調函數感受很亂,因而參考了不少有用的博客(博客原文地址會在文末給出),整理以下:javascript
咱們都知道,Javascript語言的執行環境是"單線程"(single thread)。也就是說,瀏覽器只分配給js一個主線程用來執行任務即函數,可是每次只能執行一個任務,只有等到當前任務執行完成後,才執行後面的任務,這些任務造成一個任務隊列排隊等候執行,這一點和咱們平常的排隊很像,譬如排隊買奶茶,只有等到前面一我的買完奶茶付完錢,排在他後面的人才能夠買奶茶。可是,當前面一個任務很耗時時,後面的任務就不得不等着,這時候整個程序的執行效率就會降低,就像咱們平時遇到的瀏覽器無響應即頁面假死每每是由於某段js代碼長時間運行如死循環,致使頁面卡死,後面的任務沒法執行。html
講到js的單線程,就不得不來了解一下瀏覽器前端
如圖,瀏覽器是一個多線程的執行環境,在瀏覽器的內核中分配了多個線程,其中瀏覽器常駐三大線程: js引擎線程,GUI渲染線程,瀏覽器事件觸發線程。最主要的線程之一便是js引擎的線程。因爲這三個線程同時要訪問DOM樹,因此爲了線程安全,瀏覽器內部須要作互斥即當JS引擎在執行代碼的時候,界面渲染和事件響應兩個線程是被暫停的。而因此當JS出現死循環,瀏覽器沒法響應點擊,也沒法更新界面。java
前面說到,前端會有一些任務十分耗時,而因爲js是單線程使用會下降執行效率,這些耗時的任務如網絡請求,定時器和事件監聽。因此,瀏覽器爲這些耗時任務開闢了另外的線程,主要包括http請求線程,瀏覽器定時觸發器,瀏覽器事件觸發線程,這些任務是異步的(見圖)。(詳細過程見此博文,博主講得很好~ 我的建議必須看一看哦~)ajax
說回剛剛的js單線程,爲了避免讓前面的耗時任務致使的問題出現,js的設計者把js的任務分爲同步任務和異步任務。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。如咱們剛剛所講到的瀏覽器爲網絡請求開闢的http請求線程就是異步任務。編程
具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)api
(1)全部同步任務都在主線程上執行,造成一個[執行棧]。
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
(參考與阮一峯老師的博文JavaScript 運行機制詳解:再談Event Loop)瀏覽器
有了以上了解咱們能夠知道,主線程內的同步任務執行完畢後,就會執行排在任務隊列第一位的異步任務,這個過程不斷重複。安全
當主線程開始執行異步任務,實際就是執行對應的回調函數。網絡
咱們來看一下例子:
setTimeout(function(){ console.log('Hello'); },10);
執行這段代碼,瀏覽器異步執行計時操做(注意這裏的瀏覽器模型定時計數器並非由JavaScript引擎計數的),當10ms到了以後,就會觸發定時事件,這時就會把其中的回調函數放到任務隊列中,因此當主線程空閒時在任務隊列中「讀取」而且執行的就是回調函數。
異步任務必須指定回調函數。
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。
如圖,WebAPIs就是js線程外部的api如咱們剛剛所說瀏覽器爲異步任務所開闢的線程。而任務隊列就是callbackqueue,咱們知道任務隊列中其實就是各類異步任務的回調函數,從callbackqueue的直譯「回調隊列」也可看出。而heap堆和stack棧組成了js的主線程,當stack中的函數執行完成後,就會在callbackqueue中尋找下一個任務並把它推入棧,這個尋找的過程就叫event loop(事件循環)。
看了上面你是否是對js的運行機制有了瞭解呢~
咱們把學會的知識來用一用:
提及js的異步,不少人第一反應是Ajax,但其實js中最基礎的異步就是setTimeout/setInterval。(小夥伴可不要把定時器忘了哦:))
咱們以setTimeout爲例,setTimeout接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。
咱們看看例子:
setTimeout(function(){ console.log(0); },0) console.log(1); // 1 // 0
是否是覺得打印的順序是0,1?但你們注意哦,這時候瀏覽器打印的順序是1,0。你們可能疑問了,setTimeout中設置的推遲執行的毫秒數是0呀,不就是當即執行的意思嗎。你們還記得剛剛咱們說了當有耗時任務時,會把它放在任務隊列中等待主線程空閒而後再執行,實際在執行程序的時候,瀏覽器會默認setTimeout以及ajax請求這一類的方法都是耗時程序(儘管可能不耗時),也就是上面說過的瀏覽器會爲其異步開闢線程。因此此時的setTimeout儘管它推遲時間爲0,可是js不會當即執行,而是把它加入任務隊列,當執行完執行棧的同步任務也就是打印1後,再執行setTimeout的回調函數,打印0。
總之,setTimeout(fn,0)的含義是,指定某個任務在主線程最先可得的空閒時間執行,也就是說,儘量早得執行。它在"任務隊列"的尾部添加一個事件,所以要等到同步任務和"任務隊列"現有的事件都處理完,纔會獲得執行。
因此注意的是,setTimeout()只是將事件插入了任務隊列,必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。但若是當前任務十分耗時,須要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行,好比說你指定10ms後執行,可是當前的任務執行了20ms,因此setTimeout的回調函數並不能在10ms後當即執行,可能要20ms後,若是setTimeout在任務隊列中不是排第一位,可能還不止20ms。
這個我還不是很懂~你們能夠參考阮一峯老師的Javascript異步編程的4種方法
但願一包的文章能夠幫到大家~
參考文章:
http://blog.csdn.net/qq_22855...(贊~)
https://www.cnblogs.com/woody...)
http://blog.csdn.net/kfanning...(贊~)
http://www.ruanyifeng.com/blo...(阮一峯老師嘛~)
http://www.cnblogs.com/smght/...(贊~)