1. 程式人生 > >javascirpt的閉包理解

javascirpt的閉包理解

暴露 ava arguments asc 函數名 添加 nts 返回 通過

前言

在這之前看過很多其他博客描述閉包這個概念和實際的應用,對於它的理解有了一些大概的概念,今天再重復看書,將自己對閉包的理解記錄下來,達到鞏固理解的作用。

執行環境及作用域

說到閉包,首先要先說執行環境這個概念。執行環境定義了變量和函數有權訪問的其他數據,決定了它們的行為。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和方法都保存在這個對象中。雖然我們在代碼中無法訪問這個對象,但是解析器在後臺使用它。全局執行環境就是我們所說的window對象,每一函數都有自己的執行環境,某個執行環境中所有的代碼已經執行完畢,該環境就被銷毀,保存在環境中的變量和方法也隨之一同銷毀(全局執行環境知道應用程序退出或者是關閉網頁或者瀏覽器的時候才會被銷毀)
每一個函數都以自己的執行環境,當執行流進入到一個函數之後,函數的環境就會被推入一個環境棧,在函數執行完了之後,棧將環境彈出,將控制權返回到之前的環境中。當代碼在一個環境中執行的時候,會創建變量對象的一個作用域鏈。

作用域鏈

用途:保證對執行環境有權訪問的所有變量和函數有序訪問。作用域的前端,始終都是當前執行代碼所在環境的變量對象。如果該環境是函數,就將起活動對象作為變量對象。活動對象在最開始只包括一個變量,即arguments對象(這個對象在全局對象中是不存在的)。下一個變量對象是包含的外部環境,全局執行環境的變量對象始終在作用域鏈的最末端。
內部環境可以通過作用域鏈訪問所有的外部環境,但是外部環境不可以訪問內部環境的任何一個變量和函數名。

活動對象的生存周期

一般來說,當函數執行完了之後,該執行環境就被銷毀,該環境的局部活動對象也會被銷毀,內存僅保存全局執行環境的變量對象,但是,如果函數形成閉包,即在函數中返回另外一個函數,而在這個函數中會將包含它(外部)的函數的活動對象添加到作用域鏈上,既然這個活動對象還在另外一個函數的作用域鏈下,所以它就沒有被銷毀,一直保存的內存中,直到該閉包函數被銷毀。

閉包的形成特征

  • 在一個函數中返回另外一個函數
  • 在這個另外函數可以訪問函數的局部變量和方法
  • 這些局部函數和方法會一直保存在內存中
function fn() {
    var a = 1;
    return function() {
        a++;
        return a;
    }
}
var f = fn();
f();
f();

閉包的作用

  1. 封裝變量, 把一些不需要暴露在全局的變量封裝成私有變量。

    var mult = (function() {
    var cache = {};
    return function() {
        var args = Array.prototype.join.call(arguments, ‘,‘);
        if(cache[args]) {
            return cache[args]
        };
        var a = 1;
        for(var i = 0,len = arguments.length; i < len; i++) {
            a = a * arguments[i];
        }
        return cache[args] = a;
    }
    })();

    提煉函數是代碼重構中一個常見的技巧。如果在一個大函數中有些代碼能夠獨立出來,我們就可以將它封裝在獨立的小函數中,這樣有利於函數的復用,獨立的小函數如果有一個良好的命名,本身就起到了註釋作用。如果這些小函數不需要在程序的其他地方中使用,最好是用閉包將它封裝起來。

    var mult = (function() {
    var caculate = function() { // 封閉caculate函數
        var a = 1;
        for(var i = 0, len = arguments.length; i < len; i++) {
            a = a * arguments[i];
        }
        return a;
    };
    
    return function() {
        var cache = {};
        var args = Array.prototype.join.call(arguments, ‘,‘);
        if(cache[args]) {
            return cache[args];
        }
        return cache[args] = caculate.apply(null, arguments);
    }
    })();
  2. 延續局部變量的壽命

    var report = function(src) {
    var img = new Image();
    img.src = src
    }
    report(‘http://baidu.com/getUserInfo‘);

    // 利用閉包將img變量封閉起來

    var report = (function() {
    var imgs = [];
    return function(src) {
        var img = new Image();
        imgs.push(img);
        img.src = src;
    }
    })();

閉包與內存管理

我們常說過度使用閉包會導致內存泄露,其實就是因為我們在閉包的使用過程中形成循環引用,如果閉包作用域鏈中保存著一些DOM節點,這個時候就可能會導致內存泄露。
要解決循環引用帶來的內存泄露,我們需要把循環引用中的變量設為null

function assignHandler() {
    var element = document.getElementById(‘div‘);
    element.onclick = function() {  // 創建了閉包,這個閉包又創建了一個循環引用
        alert(element.id);
    }
}

可以通過下面代碼來的解除循環引用,解決內存泄露

function assginHandler() {
    var element = document.getElementById(‘div‘);
    var id = element.id;  // 將elemnt.id的副本保存到一個變量中,解除循環引用
    element.onclick = function() {
        alert(id;)
    };

    element = null  // 解除對Dom對象的引用,解決內存泄露問題
}

註意:在上面代碼中,單單將element.id的副本保存到變量中,解除循環引用並不能夠解除內存泄露,因為閉包會引用包含函數的整個活動對象,而其中包含element,即使閉包中不直接使用element,所以有必要手動將element設置為null。

javascirpt的閉包理解