[JS基礎回顧] 閉包 又雙叒叕來~~~

語言: CN / TW / HK

highlight: arta theme: jzman


目錄

  1. 閉包的解釋
  2. 閉包暴露``函式作用域3種方式
  3. 關於閉包的記憶體洩露
  4. 迴圈和閉包

一 閉包的解釋

閉包是基於詞法作用域書寫程式碼時所產生的自然結果,你甚至不需要為了利用它們而有意識地建立閉包

MDN的解釋閉包是函式宣告該函式的詞法環境的組合。

Tips: 詞法作用域詞法環境 1,此時函式還沒被執行,所以使用的是詞法作用域即靜態作用域.2, 此時函式被執行,此時詞法作用域就會變成詞法環境(包含靜態作用域與動態作用域)

以上的解釋 個人感覺還是不夠清晰 我這樣理解 1. 閉包就是突破了函式作用域 2. 閉包就是函式巢狀函式, 子函式可以訪問父函式的變數(也就是所謂的自由變數), 所以,此變數不會被回收.

閉包暴露``函式作用域3種方式:

1) 通過外部函式的引數進行暴露

閉包內 呼叫外部函式 通過外部函式的引數 暴露 閉包內 自由變數.

js function fn() { var a = 2; function innerFn() { outerFn(a) //通過外部函式的引數進行暴露 } innerFn(); }; function outerFn(val) { console.log(val); // 2 } fn(); // 2

2) 通過外部作用域的變數進行暴露

其中val為全域性變數

```js function fn() { var a = 1; function innerFn() { val = a; //通過外部作用域的變數進行暴露 } innerFn(); };

fn(); console.log(val); // 1

```

3) 通過return直接將整個函式進行暴露

```js function fn() { var a = 1; function innerFn() { console.log(a); } return innerFn; //通過return直接將整個函式進行暴露 };

let a = fn(); a(); // 1 ```

關於閉包的記憶體洩露

首先必須宣告一點:使用閉包並不一定會造成記憶體洩露,只有使用閉包不當才可能會造成記憶體洩露.

為什麼閉包可能會造成記憶體洩露呢?原因就是上面提到的,因為它一般會暴露自身的作用域給外部使用.如果使用不當,就可能導致該記憶體一直被佔用,無法被JS的垃圾回收機制回收.就造成了記憶體洩露.

注意: 即使閉包裡面什麼都沒有,閉包仍然會隱式地引用它所在作用域裡的所用變數. 正因為這個隱藏的特點,閉包經常會發生不易發現的記憶體洩漏問題.

常見哪些情況使用閉包會造成記憶體洩露:

    1. 使用定時器未及時清除.因為計時器只有先停止才會被回收.所以決辦法很簡單,將定時器及時清除,並將造成記憶體的變數賦值為null(變成空指標)
    1. 相互迴圈引用.這是經常容易犯的錯誤,並且也不容易發現.
    1. 閉包引用到全域性變數上.因為全域性變數是隻有當頁面被關閉的時候才會被回收.

四 迴圈和閉包

1) 同步迴圈列印 正確的值

js for (var i=1; i<5; i++) { console.log( i ); } // 1 2 3 4

2) 同步中巢狀非同步任務(中的巨集任務)迴圈列印 錯誤的值

當執行 console 時, 迴圈已經完成, 同步任務執行完成後,執行巨集任務,此時 i 已經是 5.所以列印5個5.

js for (var i=1; i<5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); } // 打印出 5 個 5

3) 創造5個獨立的函式作用域,但是 i 也全都是對外部作用域的引用 錯誤的值

它的最終值仍然是5個5.為什麼?我們來分析下,它用了一個匿名函式包裹了定時器,並立即執行.在進行for迴圈時,會創造5個獨立的函式作用域(由匿名函式建立的,因為它是閉包函式).但是這5個獨立的函式作用域裡的i也全都是對外部作用域的引用.即它們訪問的都是i的最終值5.這並不是我們想要的,我們要的是5個獨立的作用域,並且每個作用域都儲存一個"當時"i的值.

js for (var i=1; i<5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); } // 打印出 5 個 5

4) 通過匿名函式建立獨立的函式作用域,並且通過變數儲存獨立的 i 值

```js for (var i=1; i<5; i++) { (function () { var x=i; console.log(x1000); // 1000 2000 3000 4000 setTimeout( function timer() { console.log( x ); }, x1000 ); })(); }

// 1 2 3 4 ```

5) 通過匿名函式建立獨立的函式作用域,並且通過引數儲存獨立的 i 值

```js for (var i=1; i<5; i++) { (function (x) { console.log(x1000); // 1000 2000 3000 4000 setTimeout( function timer() { console.log( x ); }, x1000 ); })(i); }

// 1 2 3 4 ```

參考

注意

  • 使用定時器未及時清除.因為計時器只有先停止才會被回收.所以決辦法很簡單,將定時器及時清除,並將造成記憶體的變數賦值為null(變成空指標)
  • 閉包引用到全域性變數上.因為全域性變數是隻有當頁面被關閉的時候才會被回收.
  • 閉包就是函式巢狀函式, 子函式可以訪問父函式的變數(也就是所謂的自由變數), 所以,此變數不會被回收.
  • 另外,閉包被問到,一定要說到作用域 與 執行上下文, AO VO 物件.可參考我的另一文章, 【JS引擎】解析過程