天天看點

深入剖析 JavaScript 閉包

🌞 深入剖析 JavaScript 閉包

💎導讀目錄

  • 什麼是閉包
  • 閉包的特性
  • 閉包的優缺點
  • 閉包的作用
  • 閉包的注意點

💎什麼是閉包?

❝一個函數和對其周圍狀态的引用捆綁在一起,這樣的組合就是「閉包」.

通俗的說:一個内層函數可以通路外層函數的作用域 就叫 「閉包」。

在 JavaScript 中,每當建立一個函數,閉包就會在函數建立的同時被建立出來。

閉包的形成與變量的作用域以及變量的生命周期密切相關。

💎閉包的特性

  1. 函數嵌套函數
  2. 函數内部可以引用外部的參數和變量
  3. 參數和變量不會被垃圾回收機制回收

💎閉包的優缺點

❝優點:

❝可以設計私有的方法和變量

「缺點」

❝常駐記憶體,會增大記憶體使用量,使用不當很容易造成記憶體洩露。

「一般函數執行完畢後,局部活動對象就被銷毀,記憶體中僅僅儲存全局作用域。」

💎關于 變量

變量的作用域

❝變量的作用域:變量的有效範圍。

在實際開發中,我們經常遇到的是 「函數中聲明的變量作用域。」

var a = '閉包';

function getValue(){
    var a = '函數局部作用域'
    console.log(a)
}

getValue()  //函數局部作用域

           

複制

❝當在全局聲明了一個同名變量,在函數内部也聲明了一個同名變量,函數優先通路函數作用域中的變量。

函數作用域

❝函數作用域:在函數内部可以通路到函數外部變量,而在函數外部的變量不可以通路函數内部的變量。

為什麼呢?

「因為當在函數中搜尋一個變量的時候,如果函數内部沒有這個變量的聲明,那麼它會随着代碼的執行環境建立的作用域往外層逐層搜尋,直到搜尋到全局變量為止。」

變量的搜尋是從内到外搜尋的。

function getData() {
    var str = "閉包練習";
    var fun = function(){
        var innerStr = '内部變量'
    }
    console.log(innerStr) 
     //innerStr is not defined 函數外層是通路不到 函數内層變量的
}
getData()
           

複制

變量的生存周期

❝對于 「全局變量」,它的生存周期是永久的的,除非主動銷毀變量。

而對于 「函數局部變量」 ,當函數執行完畢,局部變量也就銷毀了。

栗子 1
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>

    <script>
        var nodes = document.getElementsByTagName('div')
        for (var i = 0; i < nodes.length; i++) {
                nodes[i].onclick = function () {
                    alert(i)
                }
        }
    </script>
</body>

</html>
           

複制

❝給每個

div

增加點選事件,當點選 div 時,彈出它對應的索引值。

現在無論點選哪個

div

,它 彈出的 都是 4 。

為什麼呢?

「因為 div 點選事件 是被 異步觸發的,當事件被觸發的時候,循環已經執行完,此時的 i 的 變量值 為 4。」

如何解決 點選每個div 彈出對應的i 值呢 ?

**可以借用 閉包, 把每次循環的 i 儲存起來,當執行點選事件時,它會從内到外 搜尋變量的作用域,它會優先搜尋到 閉包環境環境的 i **

# 閉包解決辦法   
<script>
        var nodes = document.getElementsByTagName('div')
        for (var i = 0; i < nodes.length; i++) {
            (function(i) {
                nodes[i].onclick = function () {
                    alert(i)
                }
            })(i)
        }
 </script>
           

複制

栗子 2
var num = 1;
function getValue(){
    var num = 0;
    return function(){
        num++
        console.log(num)
    }
}

var s = getValue()
s()
s()
// 1 2 
           

複制

❝按常理思路來:函數執行完畢,num = 1 銷毀,變為初始值 num = 0 ,變量在函數中作用域從内到外逐層搜尋。

前面也說到了,當函數執行完,局部變量也跟着銷毀了,那為什麼會 輸出 2 呢 ?

❝這裡 涉及到 垃圾回收機制引用計數問題

[關于垃圾回收] https://blog.csdn.net/zhouziyu2011/article/details/61201613

簡述:

「當聲明了一個變量并将一個引用類型值賦給該變量時,則該值的引用次數就是1;如果同一個值又被賦給另一個變量,則該值的引用次數加1;如果包含對該值引用的變量又取得了另外一個值,則該值的引用次數減1。當該值的引用次數變為0時,則可以回收其占用的記憶體空間。當垃圾回收器下一次運作時,就會釋放那些引用次數為0的值所占用的記憶體。」

「解答」

❝第一次執行

s()

時,num = 1

第二次 執行

s()

時, 由于 引用的時第一次

s ()

的變量num=1,num 沒有被銷毀,固然在 num = 1 的基礎上 再 加 1 。

「注意」

如果沒有使用同樣引用的話,那麼多次調用,都是同樣的值,因為沒有記錄引用值。

函數在執行完畢,num = 1 被銷毀掉了,初始為 0

var num = 1;
function getValue(){
    var num = 0;
    return function(){
        num++
        console.log(num)
    }
}

getValue()()
getValue()()
// 0 0
           

複制

💎閉包的作用

❝閉包的注意作用為這兩項:
  1. 「可以讀取函數内部的變量」
  2. 「可以變量的值始終保持在記憶體中」

栗子

function f2(){
    let num = 0;
    addNum = function(){
        num++
    }
    function f3(){
        console.log(num)
    }
    return f3
}

var a = f2()
a()
addNum()
a()
// 0  1 
           

複制

結果為 0 1

函數在執行完畢,局部變量也跟着銷毀, 結果 不應該是 0 0 嗎 ?

其實a() 相當于 是 f3() 的閉包函數,它被執行了兩次。

  • 第一次 執行 a() 時, 結果為 0 , 很好了解。
  • 第二次 執行的

    f2()

    函數内部的

    addNum

    函數,發現沒這個匿名函數指派給一個變量,而且這個變量沒加

    var / let

    , 那麼它此時的作用域為

    全局

    ,儲存在記憶體當中。執行

    addNum

    時它通路的

    f2()

    函數内部的局部變量

    num

    , 此時,

    addNum

    的存在依賴于

    f2

    ,是以

    f2

    也在記憶體中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。
  • 第三次 執行

    a()

    時, 因為

    num

    已存在記憶體中,而值為1

最終輸出結果:0 , 1

💎閉包注意

  1. 由于閉包會使得函數中的變量都被儲存在記憶體中,記憶體消耗很大,是以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導緻記憶體洩露。解決方法是,在退出函數之前,将不使用的局部變量全部删除。
  2. 閉包會在父函數外部,改變父函數内部變量的值。是以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把内部變量當作它的私有屬性(private value),這時一定要小心,不要随便改變父函數内部變量的值。