Javascript 閉包

閉包的做用域鏈

閉包是有權訪問另外一個函數做用域中的變量的函數,好比:閉包

function createFunc(words) {
    return function() {
        return words;
    }
}
var func = createFunc("Hello World!")
func();
// "Hello World!"

上述例子中createFunc的返回值是一個函數(閉包),這個返回值在調用時仍然能夠訪問createFuncwords屬性,這是爲何呢?還記得在以前的文章Javascript 變量、做用域和內存問題中提到的,一個函數在建立時,會生成一個內部屬性[[scope]],這個屬性包含函數被建立的做用域中對象的集合,也就包括了createFunc的活動對象,而若是沒有閉包,createFunc的活動對象在調用結束時就能夠進入GC序列,只有銷燬對閉包的引用,即func = null,纔會使createFunc的活動對象被GC。
總結閉包的做用域鏈以下圖:
函數

經過上述分析咱們還能夠看出,閉包有一個不一樣於普通函數的特性,就是它會攜帶包含它的函數的做用域,所以會佔用更多內存。學習

閉包與變量

閉包的做用域鏈決定了閉包只能包含外部函數中任何變量的最終值,舉個例子:this

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var result = createFunctions();
result[3](); // 10

上述例子中,咱們在createFunctions函數中,經過for循環,建立了多個函數,並指望每個都能返回建立它時的索引值,但結果發現,每個函數都只能返回i的最終值(因爲ECMAScript中沒有塊級做用域,所以icreateFunctions中的局部變量),之因此是這樣的結果,是由於每個閉包可以訪問到的i都是對局部變量i的引用,因爲這種狀況的存在,所以咱們發現不少閉包都是這樣寫的:spa

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function() {
                return num;
            }    
        }(i);
    }
    return result;
}
var result = createFunctions();
result[3](); // 3

原理就是將動態的局部變量參數化,這樣每個閉包都保存了該局部變量某個時刻的副本。code

在閉包中使用this

this是基於執行環境綁定的,若是是全局函數,那麼thiswindow,若是是某個對象的函數,那麼this指這個對象,而匿名函數的執行環境具備全局性,所以閉包中的this通常指window,好比:對象

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
console.log(object.getNameFunc()()); //"The Window"

上述例子中,閉包是在全局環境中執行的,而咱們知道,每一個函數在執行時會基於執行環境自動得到this,所以this指向了window
上述例子中,如何使閉包能夠訪問object呢,能夠作以下修改:索引

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
object.getNameFunc()()
// "My Object"

內存泄漏

在以前的文章Javascript 變量、做用域和內存問題提到過IE在版本9以前,ECMAScript對象和DOM對象的GC機制不一樣,循環引用會致使DOM對象永遠不能被回收,學習完這一章節後才發現本身常常寫的一段代碼就存在這樣的問題!-_-,書中也提到了這個例子:事件

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        console.log(element.id);
    };
}

上面的代碼就建立了一個閉包做爲element的事件處理程序,這裏的循環引用體如今element的屬性onclick的值中存在對element的引用,即便退出assignHandlerelement這個DOM對象也不會被引用計數機制GC,那麼不在閉包中顯式地引用element,總能夠了吧,就好比:ip

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        console.log(id);
    };
}

答案是不行的,由於閉包中不管如何都是要保存一份對 assignHandler活動對象的引用的,天然包含element
以前提到過,因爲閉包中保存的只是函數活動對象的引用,那麼閉包中可以訪問的變量就具備動態性,上個例子中,閉包因爲引用了assignHandler的活動對象,就引用了element,而element引用了一個DOM對象,那麼,若是element不引用DOM對象,而是其餘對象,好比null,那麼element就能夠被標記清楚機制GC。

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        console.log(id);
    };
    element = null;
}
相關文章
相關標籤/搜索