溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎樣理解JavaScript 閉包

發(fā)布時間:2021-09-30 10:08:27 來源:億速云 閱讀:147 作者:柒染 欄目:編程語言

怎樣理解JavaScript 閉包,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

 深入剖析 JavaScript 閉包

    什么是閉包?
?  

一個函數(shù)和對其周圍狀態(tài)的引用捆綁在一起,這樣的組合就是「閉包」.

通俗的說:一個內(nèi)層函數(shù)可以訪問外層函數(shù)的作用域 就叫 「閉包」。

在 JavaScript 中,每當(dāng)創(chuàng)建一個函數(shù),閉包就會在函數(shù)創(chuàng)建的同時被創(chuàng)建出來。

 
閉包的形成與變量的作用域以及變量的生命周期密切相關(guān)。
?    

閉包的特性

?  
  1. 函數(shù)嵌套函數(shù)

  2. 函數(shù)內(nèi)部可以引用外部的參數(shù)和變量

  3. 參數(shù)和變量不會被垃圾回收機(jī)制回收

?    

閉包的優(yōu)缺點(diǎn)

?  

優(yōu)點(diǎn):

?

可以設(shè)計私有的方法和變量

?  

「缺點(diǎn)」

?

常駐內(nèi)存,會增大內(nèi)存使用量,使用不當(dāng)很容易造成內(nèi)存泄露。

?  

「一般函數(shù)執(zhí)行完畢后,局部活動對象就被銷毀,內(nèi)存中僅僅保存全局作用域?!?/strong>

?  
 

關(guān)于 變量

 
變量的作用域
?  

變量的作用域:變量的有效范圍。

在實(shí)際開發(fā)中,我們經(jīng)常遇到的是 「函數(shù)中聲明的變量作用域?!?/strong>

?  
var a = '閉包';

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

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

 
?  

當(dāng)在全局聲明了一個同名變量,在函數(shù)內(nèi)部也聲明了一個同名變量,函數(shù)優(yōu)先訪問函數(shù)作用域中的變量。

?  
 
函數(shù)作用域
?  

函數(shù)作用域:在函數(shù)內(nèi)部可以訪問到函數(shù)外部變量,而在函數(shù)外部的變量不可以訪問函數(shù)內(nèi)部的變量。

 
為什么呢?

「因為當(dāng)在函數(shù)中搜索一個變量的時候,如果函數(shù)內(nèi)部沒有這個變量的聲明,那么它會隨著代碼的執(zhí)行環(huán)境創(chuàng)建的作用域往外層逐層搜索,直到搜索到全局變量為止。」

變量的搜索是從內(nèi)到外搜索的。

?  
function getData() {
    var str = "閉包練習(xí)";
    var fun = function(){
        var innerStr = '內(nèi)部變量'
    }
    console.log(innerStr) 
     //innerStr is not defined 函數(shù)外層是訪問不到 函數(shù)內(nèi)層變量的
}
getData()
   
變量的生存周期
?  

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

而對于 「函數(shù)局部變量」 ,當(dāng)函數(shù)執(zhí)行完畢,局部變量也就銷毀了。

?  
 
栗子 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 增加點(diǎn)擊事件,當(dāng)點(diǎn)擊 div 時,彈出它對應(yīng)的索引值。

現(xiàn)在無論點(diǎn)擊哪個 div ,它 彈出的 都是 4 。

 
為什么呢?

「因為 div 點(diǎn)擊事件 是被 異步觸發(fā)的,當(dāng)事件被觸發(fā)的時候,循環(huán)已經(jīng)執(zhí)行完,此時的 i 的 變量值 為 4。」

 
如何解決 點(diǎn)擊每個div 彈出對應(yīng)的i 值呢 ?

**可以借用 閉包, 把每次循環(huán)的 i 保存起來,當(dāng)執(zhí)行點(diǎn)擊事件時,它會從內(nèi)到外 搜索變量的作用域,它會優(yōu)先搜索到 閉包環(huán)境環(huán)境的 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 
 
?  

按常理思路來:函數(shù)執(zhí)行完畢,num = 1 銷毀,變?yōu)槌跏贾?num = 0 ,變量在函數(shù)中作用域從內(nèi)到外逐層搜索。

前面也說到了,當(dāng)函數(shù)執(zhí)行完,局部變量也跟著銷毀了,那為什么會 輸出 2 呢 ?

?

這里 涉及到 垃圾回收機(jī)制引用計數(shù)問題

[關(guān)于垃圾回收]   https://blog.csdn.net/zhouziyu2011/article/details/61201613

簡述:

「當(dāng)聲明了一個變量并將一個引用類型值賦給該變量時,則該值的引用次數(shù)就是1;如果同一個值又被賦給另一個變量,則該值的引用次數(shù)加1;如果包含對該值引用的變量又取得了另外一個值,則該值的引用次數(shù)減1。當(dāng)該值的引用次數(shù)變?yōu)?時,則可以回收其占用的內(nèi)存空間。當(dāng)垃圾回收器下一次運(yùn)行時,就會釋放那些引用次數(shù)為0的值所占用的內(nèi)存?!?/strong>

?  

「解答」

?

第一次執(zhí)行 s() 時,num = 1

第二次 執(zhí)行 s() 時, 由于 引用的時第一次 s () 的變量num=1,num 沒有被銷毀,固然在 num = 1 的基礎(chǔ)上 再 加 1 。

?  

「注意」

如果沒有使用同樣引用的話,那么多次調(diào)用,都是同樣的值,因為沒有記錄引用值。

函數(shù)在執(zhí)行完畢,num = 1 被銷毀掉了,初始為 0

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

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

????閉包的作用

?  

閉包的注意作用為這兩項:

  1. 「可以讀取函數(shù)內(nèi)部的變量」
  2. 「可以變量的值始終保持在內(nèi)存中」
?  
 
栗子
function f2(){
    let num = 0;
    addNum = function(){
        num++
    }
    function f3(){
        console.log(num)
    }
    return f3
}

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

結(jié)果為 0 1

函數(shù)在執(zhí)行完畢,局部變量也跟著銷毀, 結(jié)果 不應(yīng)該是  0  0 嗎 ?

其實(shí)a() 相當(dāng)于 是 f3() 的閉包函數(shù),它被執(zhí)行了兩次。

  • 第一次 執(zhí)行 a() 時, 結(jié)果為 0 , 很好理解。
  • 第二次   執(zhí)行的      f2() 函數(shù)內(nèi)部的      addNum 函數(shù),發(fā)現(xiàn)沒這個匿名函數(shù)賦值給一個變量,而且這個變量沒加        var / let , 那么它此時的作用域為      全局 ,保存在內(nèi)存當(dāng)中。執(zhí)行      addNum 時它訪問的      f2() 函數(shù)內(nèi)部的局部變量      num , 此時,      addNum 的存在依賴于      f2,因此      f2 也在內(nèi)存中,不會在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收。
  • 第三次        執(zhí)行        a()       時, 因為      num       已存在內(nèi)存中,而      值為1

最終輸出結(jié)果:0 , 1

?  


    閉包注意
  1. 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
  2. 閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
?  

看完上述內(nèi)容,你們掌握怎樣理解JavaScript 閉包的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI