從setTimeout看js函數執行

  老實說,寫這篇文章的時候內心是有點壓抑的,由於受到打擊了,爲何?就 由於喜歡折騰不當心看到了這個"簡單"的函數:javascript

        for (var i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i)
            }, i * 1000);
        }
        console.log(i);
  什麼?這不就是我好久以前看到的先打印一個5,再打印一個5,以後每隔一秒就打印一個5,直到打印完6個5的實現方法嗎?那麼問題來了,若是我要依次打印0,1,2,3,4,5的話我該怎麼辦,其實在這以前我就知道有這兩個方法:一個是這樣:
   function log(i){
   setTimeout(function(){
    console.log(i)
    },i*1000)
   };
  
   for (var i = 0; i < 5; i++) {
            log(i) ;
        }
        console.log(i);
   還有一個是這樣:
   for(var i=0;i<5;i++){
    (function(e){
      setTimeout(function(){
       console.log(e)
      },i*1000);
    })(i);
   };
  console.log(i);
  不怕笑話,在這以前我是沒搞懂這兩個函數真正意義上的做用是用來幹嗎的,只強迫本身這樣記住這樣修改就能夠了,可是如今不行啊,我有強迫症啊!因而,我慢慢分析了一下,發現上面那段代碼能夠分離成這樣:
  i=0時;知足條件;
  setTimeout(function(){
    console.log(i)
    },0*1000);
  
  i=1時;知足條件;
  setTimeout(function(){
    console.log(i)
    },1*1000);
  
i=2時;知足條件;
  setTimeout(function(){
    console.log(i)
    },2*1000);
  
i=3時;知足條件;
  setTimeout(function(){
    console.log(i)
    },3*1000);
i=4時;知足條件;
  setTimeout(function(){
    console.log(i)
    },4*1000);
i=5時,不知足條件,跳出循環,接着執行for循環後面的console.log(i),打印5;最後依次每秒打印5;
  真有意思,爲何setTimeout裏面的console.log會是後於for循環外面的console.log執行呢?直到我認識到了這個單詞=>"隊列", 隊列又有宏任務隊列(Macro Task)以及微任務隊列(Micro Task)之分,在javascript中:
  1. macro-task包括:script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。java

  2. micro-task包括:process.nextTick, Promises, Object.observe, MutationObserverpromise

  3. 上面函數的setTimeout就屬於宏任務

在js中,事件循環的順序是從script開始第一次循環,隨後全局上下文進入函數調用棧,碰到macro-task就將其交給處理它的模塊處理完以後將回調函數放進macro-task的隊列之中,碰到micro-task也是將其回調函數放進micro-task的隊列之中。直到函數調用棧清空只剩全局執行上下文,而後開始執行全部的micro-task。當全部可執行的micro-task執行完畢以後。循環再次執行macro-task中的一個任務隊列,執行完以後再執行全部的micro-task,就這樣一直循環。異步

 這就是爲何setTimeout裏面的console.log會是後於for循環外面的console.log執行,在函數執行上下文中,seiTimeout函數會被放處處理他的macro-task的隊列之中,因此循環的時候setTimeout裏面的function是不會被執行的,而是等到全部總體代碼(非隊列)跑完以後纔會執行隊列中的函數;寫到這裏,可能會有點懵逼,其實我也有點懵逼,哈哈哈!!函數

  爲了加深理解,還能夠試試在裏面加入Promise,因而就有了這個:spa

(function copy() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) {  i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
解釋一下=>
1.首先,script任務源先執行,全局上下文入棧。
2.script任務源的代碼在執行時遇到setTimeout,做爲一個macro-task,將其回調函數放入本身的隊列之中。
3.script任務源的代碼在執行時遇到Promise實例。Promise構造函數中的第一個參數是在當前任務直接執行不會被放入隊列之中,所以此時輸出 1 。
4.在for循環裏面遇到resolve函數,函數入棧執行以後出棧,此時Promise的狀態變成Fulfilled。代碼接着執行遇到console.log(2),輸出2。
5.接着執行,代碼遇到then方法,其回調函數做爲micro-task入棧,進入Promise的任務隊列之中,此時Promise的then 裏面的function回調函數跟setTimeout裏面的function回調函數有着殊途同歸之意,都會被放到各自的任務隊列中,
 直到函數上下文即script中全部的非隊列代碼執行完畢後再執行,並且微任務隊列優先於宏任務隊列被處理,
整體順序爲:上下文非隊列代碼>微任務隊列回調函數代碼>宏任務隊列回調函數代碼
6.代碼接着執行,此時遇到console.log(3),輸出3。
7.輸出3以後第一個宏任務script的代碼執行完畢,這時候開始開始執行全部在隊列之中的micro-task。then的回調函數入棧執行完畢以後出棧,這時候輸出5
8.這時候全部的micro-task執行完畢,第一輪循環結束。第二輪循環從setTimeout的任務隊列開始,setTimeout的回調函數入棧執行完畢以後出棧,此時輸出4。
最後,爲了加深理解,再上一段代碼:
console.log('golb1');
setTimeout(function() {
console.log('timeout1');
new Promise(function(resolve) { console.log('timeout1_promise'); resolve();
    setTimeout(function(){
      console.log('time_timeout')
    });   }).then(function() { console.log('timeout1_then') })
setTimeout(function() { console.log('timeout1_timeout1'); }); }) new Promise(function(resolve) { console.log('glob1_promise'); resolve();
  setTimeout(function(){
     console.log('prp_timeout')
    });
}).then(function() { console.log('glob1_then') }) 
若是你的執行結果是:golb1=>glob1_promise=>glob1_then=>timeout1=>timeout1_promise=>timeout1_then=>prp_timeout=>time_timeout=>timeout1_timeout1,可能異步隊列算是入門了吧!~~上面的代碼看起來有點雜亂,可能用asyns搭配await改造一下會更好,可是這或多或少是鄙人從setTimeout中獲得的看法吧,有啥不對之處望指正:另外,參考了一下別人的文章:https://zhuanlan.zhihu.com/p/26238030寫的確實不錯
相關文章
相關標籤/搜索