首頁  >  文章  >  web前端  >  關於JavaScript中閉包的詳細解

關於JavaScript中閉包的詳細解

迷茫
迷茫原創
2017-03-26 15:22:05984瀏覽

閉包是什麼

在 JavaScript 中,閉包是一個讓人很難理解的概念。 ECMAScript 中給閉包的定義是:閉包,指的是詞法表示包含不被計算的變數的函數,也就是說,函數可以使用函數之外定義的變數。

是不是看完這個定義感覺更懵逼了?別急,我們來分析一下。

  • 閉包是一個函數

  • 閉包可以使用在它外面定義的變數

  • #閉包存在定義該變數的作用域中

好像有點清晰了,但是使用在它外面定義的變數是什麼意思,我們先來看看變數作用域。

變數作用域

變數可分為全域變數和局部變數。全域變數的作用域就是全域性的,在 js 的任何地方都可以使用全域變數。在函數中使用 var 關鍵字聲明變量,這時的變量即是局部變量,它的作用域只在聲明該變量的函數內,在函數外面是訪問不到該變量的。

var func = function(){
    var a = 'linxin';
    console.log(a);         
    // linxin}func();console.log(a);             
    // Uncaught ReferenceError: a is not defined

作用域相對比較簡單,我們不多講,來看看跟閉包關係比較大的變數生存週期。

變數生存週期

全域變量,生命週期是永久的。局部變量,當定義該變數的函數呼叫結束時,該變數就會被垃圾回收機制回收而銷毀。再次呼叫函數時又會重新定義了一個新變數。

var func = function(){
    var a = 'linxin';
    console.log(a);}func();

a 為局部變量,在 func 調用完之後,a 就會被銷毀了。

var func = function(){
    var a = 'linxin';
    var func1 = function(){
        a += ' a';
        console.log(a);
    }
    return func1;}var func2 = func();func2();                    
    // linxin afunc2();                    
    // linxin a afunc2();                   
    // linxin a a a

可以看出,在第一次調用完 func2 之後,func 中的變數 a 變成 'linxin a',而沒有被銷毀。因為此時 func1 形成了一個閉包,導致了 a 的生命週期延續了。

這下子閉包就比較明朗了。

  • 閉包是一個函數,例如上面的 func1 函數

  • #閉包使用其他函數定義的變量,使其不被銷毀。例如上面 func1 呼叫了變數 a

  • 閉包存在定義該變數的作用域中,變數 a 存在 func 的作用域中,那麼 func1 也必然存在這個作用域中。

現在可以說,滿足這三個條件的就是閉包了。

下面我們透過一個簡單又經典的例子來進一步熟悉閉包。

for (var i = 0; i < 4; i++) {
    setTimeout(function () {
        console.log(i)    
     }, 0)
}

我們可能會簡單的以為控制台會印出 0 1 2 3,但事實卻印出了 4 4 4 4,這又是為什麼呢?我們發現,setTimeout 函數時異步的,等到函數執行時,for迴圈已經結束了,此時的i 的值為4,所以function() { console.log(i) } 去找變數i,只能拿到4。

我們想起上一個例子中,閉包使 a 變數的值被保存起來了,那麼這裡我們也可以用閉包把 0 1 2 3 保存起來。


for (var i = 0; i < 4; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i)        
        }, 0)   
     })(i)
 }

當i=0 時,把0 作為參數傳進匿名函數中,此時function(i){} 此匿名函數中的i 的值為0 ,等到setTimeout 執行時順著外層去找i,這時就能拿到0。如此循環,就能拿到想要的 0 1 2 3。

記憶體管理

在閉包中呼叫局部變量,會導致這個局部變數無法及時被銷毀,相當於全域變數一樣會一直佔用記憶體。如果需要回收這些變數佔用的內存,可以手動將變數設為null。

然而在使用閉包的過程中,比較容易形成 JavaScript 物件和 DOM 物件的循環引用,就有可能造成記憶體外洩。這是因為瀏覽器的垃圾回收機制中,如果兩個物件之間形成了循環引用,那麼它們都無法被回收。

function func() {
    var test = document.getElementById(&#39;test&#39;);
    test.onclick = function () {
        console.log(&#39;hello world&#39;);
    }
    }

在上面範例中,func 函數中以匿名函數建立了一個閉包。變數 test 是 JavaScript 對象,引用了 id 為 test 的 DOM 對象,DOM 對象的 onclick 屬性又引用了閉包,而閉包又可以調用 test ,因而形成了循環引用,導致兩個對像都無法被回收。要解決這個問題,只需要把循環引用中的變數設為 null 即可。

function func() {
    var test = document.getElementById(&#39;test&#39;);
    test.onclick = function () {
        console.log(&#39;hello world&#39;);
    }
    test = null;}

如果在 func 函數中不使用匿名函數建立閉包,而是引用一個外部函數,也不會出現循環引用的問題。

function func() {
    var test = document.getElementById(&#39;test&#39;);
    test.onclick = funcTest;}function funcTest(){
    console.log(&#39;hello world&#39;);
 }

以上是關於JavaScript中閉包的詳細解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn