循環中的異步&&循環中的閉包

在這以前先要了解一下javascript

for循環中let 和var的區別

var 是函數級做用域或者全局做用域,let是塊級做用域 看一個例子html

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 換爲letjava

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》上卷

相關文章
相關標籤/搜索