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


聊聊同步、異步和回調

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

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

世界上的所有事情大致可以分為同步去做和異步去做兩種。你打電話去訂酒店,電話另一邊的工作人員需要查下他們的管理系統才能告訴你有沒有房間。

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

另一種是工作人員問了你的聯系方式就掛斷了電話,等他們查到之后再通知你,這就是異步的,這時候你就可以干點其他事情,比如把機票也定了之類的

 


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

js是單線程的,如果所有的操作(ajax,獲取文件等I/O操作<node>)都是同步的,遇到哪些耗時的操作,后面的程序必然被阻塞而不能執行,頁面也就失去了響應,

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


 

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

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

(上圖轉引自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),即指定某些代碼在多少時間之后執行;

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

 2、還記的我們之前總結的執行順序:同步 > 異步 > 回調 吧!

  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博大精深,我們需要了解他的機制去深入去挖掘他。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM