白話 event loop的理解

event loop ? microtask ? macrotask ? 不好意思,我記不住,關于這方面的解釋以及深入理解一大堆的中英結合,中間穿插著各種 setImmediate、requestAnimationFrame。‘我才學,腦袋大,通俗點,886 Ctrl+W’。


我想用俗話說

我先把主要圍繞的例子列出來,也主要是通過這個最常見代碼去解釋一下我對event loop的理解:

    for(var i = 0; i < 5; i++) {
        setTimeout(function() {
              console.log(i);
        },1000*i);
  }

A:哇,你個水貨,先出來一個5 然后 一秒一個 5,面試經典題還用你教? (張學友的問候.jpg)

me: 好像還可以在深入一下....,比如:

  • 為什么每次都是5;
  • 為什么是你理解的1s一次;
  • 為什么用立即執行函數(IIFE)就可以解決, 以及ES6中的let。

我想,等我廢話說完你可能會有個大概理解了,到時候我想基于這個理解再寫一篇關于AMD的白話文實現。

上道具

我把一次任務的執行理解為上圖兩個部分

阻塞事件

js是單線程,在代碼執行階段對代碼進行分類處理,按照我的理解就是:當我找到你的時候,你就必須去做或者立刻告訴我結果。就好比我去訂餐,有沒有位子就在此次通話內告訴我。
那我們的例子來說就是:

for(var i = 0; i < 5; i++) {}  
 => var i = 0;  {做點什么}(i++);  {做點什么}(i++);  {做點什么}(i++);  {做點什么}(i++);...
//當然會說了,明明還要 i<5 的判斷了,這個里判斷也算是阻塞。很小的一部分。

這里就是 就往 阻塞隊列增加了5個事件(當然肯定不止5個,每個事件內部還有事件),并按照先進先出(堆)的原則去執行。

非阻塞事件

當程序讀取到了非阻塞事件,將該事件如加入到當前的非堵塞隊列,等待同一個任務隊列中的阻塞隊列執行結束后,再去執行非阻塞隊列中的任務,當然也是先進先出

 setTimeout(function(){console.log('1')},500);      //A
 setTimeout(function(){console.log('2')},100);      //B
=>
2
1

不要以為B比A先出來,就以為B比A先執行。這個,肯定要講。
根據列子來說:

     setTimeout(function() {
              console.log(i);
        },1000*i);

// 就是往 非阻塞隊列 添加了 五個非阻塞時間
// 這個時候我們一定要區分 console.log(i)與 setTimeout(fn,1000*i)  中的 i;
//當然他們是同一個i,只是時刻不同,那么時刻不用是什么?

  • console.log(i)中的 i: 是當這個非阻塞事件可以執行的時候去獲取的 i
  • setTimeout(fn,1000*i)中的 i : 是注冊這個非阻塞事件并向非阻塞事件隊列添加的時候的 i,這個I就是0 1 2 3 4(這一個跟開始我拋出的,為什么是你理解的1秒1次5有關);

整體分析

圖解任務
  • 首先必須明確的是,非阻塞隊列只有在對應的阻塞隊列執行完畢后才能執行。
  • 非阻塞隊列,并不一定是一次循環執行就能結束的,也就是說按照注冊順序執行,滿足條件的事件執行回調并移除該隊列。

解答

為什么輸出console.log(i),輸出5個5;

因為所有非阻塞隊列中的fn0-fn4中都是 console.log(i);
根據js設計思想,只有當用到某個變量時,才去尋找這個變量的值,這個時候非阻塞隊列是在阻塞隊列執行完全之后再執行的;
那么這個時候的i已經經過了5個for循環5個i++從一開始的0變成了5,所以在fn0-fn5執行的時候console.log(i),i 的值 已經是5了。

為什么是你理解的1s一個5

首先它的確是 0s 5 ;1s 5 ;2s 5 ;3s 5 ;4s 5 ;
為什么又是1s一個5呢? 參照物
當你根據每個console .log(i)出現為基準,就如同大家都在一個房間,A出門走1步,B出門走2步,C出門走3步。。。
相對于ABC...每個人來說,他們之間的確相差了1步
但是相對這個房間來說,ABC...每個人相對于房間可就不是固定的1步了。
我在阻塞隊列注冊了五個非阻塞事件,誰告你這幾個setTimeout之間有關系的。。。,
我一個人玩的setTimeout為什么要根據你的setTimeout結束才能玩?

解答:
阻塞隊列執行的時候,注冊了五個非阻塞事件,例如:
阻塞事件中通過setTimeout注冊了一個非阻塞事件,以這個注冊時間為基準,至少1000*i ms(這里的i肯定0 1 2 3 4 而不一直都是5,因為我注冊事件的時候就要用到i,那就要拿到當時的i值)之后去執行這個fn(setTimeout里面的fn回調函數);
那么五次循環之后(阻塞隊列執行完畢),實際上就是已經注冊了五個不同時間節點非阻塞事件,雖然說可以說五個非阻塞事件注冊時間基本上是同一個時間點,但是程序執行的確是要花費時間的,只能說太快,事件太少,所以我們基本可以理解為這個5個setTimeout注冊幾乎就在一個房間,而每次注冊的時候已經告訴了這個回調函數至少多少ms之后執行(就是告訴ABC...要相對這個房間走多少步)。
所以,他們的確是針對注冊這個非阻塞事件時 , 0s 5 ;1s 5 ;2s 5 ;3s 5 ;4s 5 ;

為什么用立即執行函數(IIFE)就可以解決, 以及ES6中的let

因為閉包

   for(var i = 0; i < 5; i++) {
       (function(j) {
              setTimeout(function() {
                   console.log(j);
             },1000*i);
       })(i)
      
 }

實際上就是每個非阻塞事件再執行的時候去查找變量,通過IIFE給setTimeout與for循環之間多加了一層向上查找變量的作用于,本來是

for循環為每個setTimeout多提供了一層獨有的閉包環境

其實你可以試試 把加入 IIFE 的console.log( j )變成console.log( i );
結果就還是 ** 5 個 5 因為 IIFE提供的閉包環境中 只提供了一個 j 變量, 當沒有找到 i 時 ,又得向上查找,這個時候就找到了阻塞事件**執行完畢之后的 i ,那還不就 5 嘛~

let 其實就是讓每個for循環有自己作用域塊,從** 5 個 console.log(i)對應一個 ** i (不加let)變成 5 個 console.log(i)對應5個 i ,這每個 i 并不是同一個了。


結束

寫到這里,其實已經很長了,再寫下去可能就要開始乏味了。那我再說兩個小點吧

  • promise : 其實就是 阻塞事件干兒子,不管setTimeout在promise之前多少(當然我們這里說的是同一個任務),每次去注冊非阻塞事件的時候,promise肯定是在阻塞隊列結束之后立即去執行的,就好比 阻塞事件每次執行完了就會說 “干兒子(promise),我好了你趕緊到我后面來,別讓setTimeout它們先跑了”
  • setTimeout(fn,times),setInterval(fn,times): 它們中的 times,并不是說 隔 **xxx ms ** 后去執行,而是至少 xxx ms 只執行,為什么呢,說太多寫不下,自己慢慢體會。
溜了溜了
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容