閉包與作用域再次深究

  • 2019 年 10 月 3 日
  • 筆記

最近看到一個有意思的函數

        function test (arr) {              var temp = []              for (var i =0;  i<arr.length; i++) {                  (function() {                      var j = i;                      temp[i] = function () {                          return j                      }                  })()                }              return temp          }  

那麼以下的結果會列印出什麼呢?

var arr = [1,2,3,4,5]  var arrFn = test(arr)  console.log(arrFn[0])  

結果是:0;

那麼繼續test函數換成以下兩種又會是什麼結果呢?

        function test2 (arr) {              var temp = []              for (var i =0;  i<arr.length; i++) {                    temp[i] = function () {                      return i                  }                }              return temp          }

        function test3 (arr) {              var temp = []              for (var i =0;  i<arr.length; i++) {                  (function() {                      temp[i] = function () {                          return i                      }                  })()                }              return temp          }

test2是網上比較常見的,結果是我們在讀取i的時候,i已經全部變為5;test3和test只有兩行程式碼不同,但是結果卻完全不一樣。

之前曾經理解是因為我們調用的時候for循環已經執行完,所以會拿到i為5。但是其實這只是表象,最核心的其實只有一句話:

閉包通過引用而不是值(非引用)來獲取他們外部的變數

因為是引用類型,所以在for執行完之後,i變為了5,引用類型也就繼而變為5.

在test中添加了一行可以驗證的程式碼就是:

var j = i;

通過在局部作用域聲明非引用類型,將單次循環是的引用類型保存下來,那麼就能獲取我們想要的結果了。

還有其他方法,通過自執行函數傳遞參數,將引用類型變為非引用類型都是一個原理:

        function test4 (arr) {              var temp = []              for (var i =0;  i<arr.length; i++) {                  (function(j) {                      temp[i] = function () {                          return j;                      }                  })(i)                }              return temp          }  

其次就是在IIFE來創建局部作用域的時候,需要注意不能再作用域外部使用break與continue,這樣的寫法是不合法的。