迴圈中的非同步&&迴圈中的閉包

陌上寒發表於2019-03-04

在這之前先要了解一下

for迴圈中let 和var的區別

var 是函式級作用域或者全域性作用域,let是塊級作用域 看一個例子

    function foo() {
      for (var index = 0; index < array.length; index++) {
        //..迴圈中的邏輯程式碼
      }
      console.log(index);//=>5
    }
    foo()
   console.log(index)//Uncaught ReferenceError: index is not defined
複製程式碼

foo函式下的index輸出5,全域性下的index不存在 現在我們把var 換為let

    function foo() {
      for (let index = 0; index < array.length; index++) {
        //..迴圈中的邏輯程式碼
      }
      console.log(index)//Uncaught ReferenceError: index is not defined
    }
    foo()
複製程式碼

報錯了,index不在foo函式作用域下,當然肯定也不會再全域性下 因為var和let的這個區別(當然var和let的區別不止於此)所以導致了下面的這個問題 關於var的

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (var index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000);
      }
    }
    foo()
複製程式碼

看下輸出

迴圈中的非同步&&迴圈中的閉包
關於let的

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (let index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000);
      }
    }
    foo()
複製程式碼

看下輸出

迴圈中的非同步&&迴圈中的閉包
因為var和let 在作用域上的差別,所以到這了上面的問題 使用var 定義變數的時候,作用域是在foo函式下,在for迴圈外部,在整個迴圈中是全域性的,每一次的迴圈實際上是為index賦值,迴圈一次賦值一次,5次迴圈完成,index最後的結果賦值就為5;就是被最終賦值的index,就是5; let的作用局的塊級作用局,index的作用域在for迴圈內部,即每次迴圈的index的作用域就是本次迴圈,下一次迴圈重新定義變數index;所以index每次迴圈的輸出都不同 這裡還有另外一個問題,setTimeout,這是一個非同步,這就是我們今天要討論的

迴圈中的非同步

setTimeout(func,time)函式執行機制

setTimeout(func,time)是在time(毫秒單位)時間後執行func函式。瀏覽器引擎按順序執行程式,遇到setTimeout會將func函式放到執行佇列中,等到主程式執行完畢之後,才開始從執行佇列(佇列中可能有多個待執行的func函式)中按照time延時時間的先後順序取出來func並執行。即使time=0,也會等主程式執行完之後,才會執行。

一個需求,一個陣列array[1,2,3,4,5],迴圈列印,間隔1秒

上面的let是迴圈列印了12345,但是不是間隔1s列印的,是在foo函式執行1s後,同時列印的

方式一 放棄for迴圈,使用setInterval

    function foo(){
      let index = 0;
      const array = [1, 2, 3, 4, 5]

      const t = setInterval(()=>{
        if (index < array.length) {
          console.log(array[index]);
        }
        index++;
      }, 1000);

      if (index >= array.length) {
        clearInterval(t);
      }
    }
    foo()
複製程式碼

我們上面說到,當for迴圈遇到了var,變數index的作用域在foo函式下,迴圈一次賦值一次,5次迴圈完成,index最後的結果賦值就為5;就是被最終賦值的index,就是5;

方式二,引入全域性變數

程式碼執行順序是,先同步執行for迴圈,再執行非同步佇列,在for迴圈執行完畢後,非同步佇列開始執行之前,index經過for迴圈的處理,變成了5。 所以我們引入一個全域性變數j,使j在for迴圈執行完畢後,非同步佇列開始執行之前,依然是0,在非同步執行時進行累加

    var j = 0;
    for (var index = 0; index < array.length; index++) {
      setTimeout(() => {
        console.log(j);
        j++;
      }, 1000 * index)
    }
複製程式碼

方式三 for迴圈配合setTimeout(常規思路,不贅述,面試必備技能)

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (let index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000*index);
      }
    }
    foo()
複製程式碼

方式四,通過閉包實現

開始討論方式四之前我推薦先閱讀一遍我之前寫過一篇文章 談一談javascript作用域 我們對上面的問題再次分析,for迴圈同步執行,在for迴圈內部遇到了setTimeout,setTimeout是非同步執行的,所以加入了非同步佇列,當同步的for迴圈執行完畢後,再去執行非同步佇列,setTimeout中有唯一的一個引數數index 方式三可行,是因為let是塊級作用域,每次for執行都會建立新的變數index,for迴圈執行完畢後,非同步執行之前,建立了5個獨立的作用域,5個index變數,分別是0,1,2,3,4,相互獨立,互不影響,輸出了預期的結果 如果說每次迴圈都會生成一個獨立的作用域用來儲存index,問題就會得到解決,所以,我們通過閉包來實現

    const array = [1, 2, 3, 4, 5]

    function foo() {
      for (var index = 0; index < array.length; index++) {
        function fun(j) {
          setTimeout(function () {
            console.log(j);
          }, 1000 * j);
        }
        fun(index)
      }
    }
    foo()
複製程式碼

setTimeout中的匿名回撥函式中引用了函式fun中的區域性變數j,所以當fun執行完畢後,變數j不會被釋放,這就形成了閉包 當然我們可以對此進行一下優化

    const array = [1, 2, 3, 4, 5]

    function foo() {
      for (var index = 0; index < array.length; index++) {
        (function(j) {
          setTimeout(function () {
            console.log(j);
          }, 1000 * j);
        })(index)
      }
    }
    foo()
複製程式碼

將foo函式改為匿名的立即執行函式,結果是相同的

總結

for迴圈本身是同步執行的,當在for迴圈中遇到了非同步邏輯,非同步就會進入非同步佇列,當for迴圈執行結束後,才會執行非同步佇列 當非同步函式依賴於for迴圈中的索引時(一定是存在依賴關係的,不然不會再迴圈中調動非同步函式)要考慮作用域的問題, 在ES6中使用let是最佳的選擇, 當使用var時,可以考慮再引入一個索引來替代for迴圈中的索引,新的索引邏輯要在非同步中處理 也可以使用閉包,模擬實現let 在實際開發過程中,迴圈呼叫非同步函式,比demo要複雜,可能還會出現if和else判斷等邏輯,具體的我們下次再續

參考

通過for迴圈每隔兩秒按順序列印出arr中的數字

setTimeOut和閉包

《你不知道的JavaScript》上卷

相關文章