javascript中對於閉包理解的講述

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

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

閉包是一個函數

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

閉包存在定義該變量的作用域中

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

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

變量生存周期

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

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

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

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

可以看出,在第一次調用完 func2 之後,func 中的變量 a 變成 ‘xiaxiaoxian 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) {  // 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('test');
    test.onclick = function () {
        console.log('hello world');
    }
}

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

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

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

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

You May Also Like