同步、異步、回調執行順序之經典閉包setTimeout分析

聊聊同步、異步和回調

同步,異步,回調,咱們傻傻分不清楚,node

有一天,你找到公司剛來的程序員小T,跟他說:「咱們要加個需求,你放下手裏的事情優先支持,我會一直等你作完再離開」。小T微笑着答應了,眼角卻滑過一絲不易覺察的殺意。程序員

世界上的全部事情大體能夠分爲同步去作和異步去作兩種。你打電話去訂酒店,電話另外一邊的工做人員須要查下他們的管理系統才能告訴你有沒有房間。es6

這時候你有兩種選擇:一種是不掛電話一直等待,直到工做人員查到爲止(可能幾分鐘也可能幾個小時,取決於他們的辦事效率),這就是同步的。面試

另外一種是工做人員問了你的聯繫方式就掛斷了電話,等他們查到以後再通知你,這就是異步的,這時候你就能夠乾點其餘事情,好比把機票也定了之類的ajax

 


 計算機世界也是如此,咱們寫的代碼須要交給cpu去處理,這時候就有同步和異步兩種選擇vim

js是單線程的,若是全部的操做(ajax,獲取文件等I/O操做<node>)都是同步的,遇到哪些耗時的操做,後面的程序必然被阻塞而不能執行,頁面也就失去了響應,閉包

所以js採用了事件驅動機制,在單線程模型下,使用異步回調函數的方式來實現非阻塞的IO操做異步


 

那麼什麼是異步任務呢?(參考阮一峯老師《JavaScript運行機制》)函數

異步任務也就是 指主線程(stack棧)運行的過程當中,當stack空閒的時候,主線程對event queque(隊列)輪詢(事實上一直在輪詢)後,將異步任務放到stack裏面進行執行;oop

(上圖轉引自Philip Roberts的演講《Help, I'm stuck in an event-loop》))

 簡單的說,若是咱們指定過回調函數,那麼當事件發生時就會進入事件隊列,等待主線程的(stack)空閒的時候,就會event queue裏面的回調讀取並放到stack裏面執行

咱們常常說的多是異步回調(固然也有同步回調),因此也就並不難理解,回調和異步之間其實並無直接的聯繫,回調只是異步的一種實現方式, 

經過這樣的event loop咱們其實能夠分析出三者的執行順序,即 同步 > 異步 > 回調

經典閉包setTimeout分析

今天同窗問了我一個問題,我一看是一道經典的面試題,問題以下:

簡單的這個問題改一下:

1  for (var i = 0; i <= 5; i++) {
2      setTimeout(function() {
3          console.log( i );
4      }, i*1000);
5       console.log( ' i : ' , i );
6  }
7  
8  console.log( i );

相信咱們不少人都遇到過這個問題,心中或許都有答案:

那麼爲何並非入門者心中所想要的結果嘞(爲何setTimeout中打印出i所有是6,並且是最後纔打印出來呢)?

那麼就讓咱們來梳理一下,第一部分event loop圖片很直觀的體現:"任務隊列"能夠放置異步任務的事件,也能夠放置定時事件(setTimeout和setinterval),即指定某些代碼在多少時間以後執行;

 一、首先咱們先來看一下他的主體結構: for循環的第一層是setTimeout函數,setTimeout函數中使用了一個匿名(回調)函數

 二、還記的咱們以前總結的執行順序:同步 > 異步 > 回調 吧!

  1)for循環和外層的 console.log()是同步的,setTimeout是回調執行,

  因此按照執行順序,先執行for循環,而後進入for循環中,他發現了一個setTimeout()回調(進入event queque事件隊列,等待stack棧爲空後讀取並放入棧中後執行),這時候他並不會等待

  而是繼續執行 --> for循環內部的 console.log( ' i : ' , i )  -->  for循環外部的console.log( i ) ,而後"任務隊列"中的回調函數才進入到空Stack中開始執行;


 咱們在來用這個例子嘗試一下上面的event loop圖,更加直觀的感覺一下:

 

那麼接下來可能會問怎麼解決這個問題呢?我想最簡單的固然是let語法了,

1  for (let i = 0; i <= 5; i++) {
2     setTimeout(function() {
3           console.log( i );
4       }, i*1000);
5       console.log( ' 1 : ' , i );
6   }
7   
8  console.log( i );

咱們都知道es5中變量做用域是函數,而es6卻可使用let聲明一個具備塊級做用域的i,在這裏也就是for循環體;

在這裏let本質上就是造成了一個閉包,那麼寫成es5的形式其實等價於

 1  var loop = function (_i) {
 2      setTimeout(function() {
 3          console.log( _i);
 4      }, _i*1000);
 5      console.log('2:',_i)  
6
};
7
8
for (var _i = 0; _i <= 5; _i++) {
9
loop(_i);
10 }

 總結

到這裏,咱們就完成了從同步、異步、回調的機制分析 到 setTimeout的經典案例的分析,JavaScript博大精深,咱們須要瞭解他的機制去深刻去挖掘他。

相關文章
相關標籤/搜索