javascript:閉包的總結

*前言:此次總結閉包,分別參考了《js高級程序設計》、廖雪峯老師的網站、還有《js忍着祕籍》,好了,廢話少說,黑喂狗~~~javascript

---------------------嚴肅分割線-------------------*java

1.js函數中的做用域鏈

沒錯,閉包仍是要從做用域鏈提及,要理解閉包必須從函數第一次被調用時發生了什麼入手,先看一個例子,代碼:數組

function compare(value1,value2){
        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
    var result = compare(5,10);  //全局做用域中調用

首先定義了一個compare函數,而後又在全局做用域中調用了它。當調用compare()時,會建立一個包含(this,arguments,value1,value2)的活動對象,而全局執行環境的變量對象(this,result,compare)在compare()執行環境的做用域鏈中處於第二位。在這例子中,當調用compare()函數時,會爲函數建立一個執行環境,其做用域鏈包含兩個變量對象:本地活動對象和全局對象,在函數中訪問一個變量時,會先從本地做用域中查找,找不到則向上查找外層函數的做用域中是否有,直到全局做用域。當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域,可是閉包狀況有所不一樣。閉包

2.閉包中的做用域鏈

先看一個例子:函數

function createComparisonFunction(propertyName){
        return function(object1,object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        };
    }

    var compareNames  = createComparisonFunction("name");
    var result = compareNames({name:"Jack"},{name:"Rose"});
    compareNames = null;   //銷燬匿名函數

上面的例子中,在createComparisonFunction函數內部定義了一個匿名函數,它的做用域鏈包含外部函數createComparisonFunction()的活動對象和全局變量對象,因此匿名函數中能夠訪問createComparisonFunction()函數中的propertyName。
最重要的是:createComparisonFunction()函數在執行完畢後,它的執行環境做用域鏈會被銷燬,其活動對象仍然會留在內存中,這就是爲何上面的代碼中,執行:
var compare = createComparisonFunction("name")以後,createComparisonFunction()函數已經執行完畢,可是仍然能夠在下一行代碼中執行比較大小的操做。
建立的比較函數被保存在變量compareNames中,設置它等於null,解除該函數引用,垃圾回收。測試

3.閉包與變量

反作用:

閉包只能取得外層函數中任何變量的最後一個值。看下面例子:網站

function createFunctions(){
        var arr = new Array();
        for(var i=0; i<10;i++){
            arr[i] = function(){
                return i;
            };
        }
        return arr;
    }
    
    var result = createFunctions();
    var f1 = result[0];
    var f2 = result[1];
    var f3 = result[2];
    //執行函數
    alert(f1());    //10
    alert(f2());    //10
    alert(f3());    //10

這個例子中彷佛每一個函數都應該返回本身的索引值,實際上每一個匿名函數返回的都是10。由於每一個匿名函數中都保存着createFunctions()函數的活動對象,因此它們引用的是同一個變量i,函數createFunctions()返回後,變量i的值都是10,此時每一個函數都引用着保存變量i的同一個變量對象,因此每一個函數內部i的值都是10.this

注意:上述函數的調用過程:執行createFunctions()函數,而且把函數執行結果賦值給變量result,如今result是一個數組,再把result[0]賦值給f1,f1實際上表明的是內部的匿名函數,如今執行這個函數:f1(),就會獲得i的值。prototype

改造

能夠經過建立另外一個匿名函數強制讓閉包的行爲符合預期,看下面例子:設計

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 final = createFunctions();
    var f1 = final[0];
    alert(f1());   //0

此次沒有直接把閉包賦值給數組,而是定義了一個匿名函數,並當即執行該函數的結果賦值給數組。這個匿名函數有一個參數num,也就是最終要返回的值,調用這個匿名函數時,傳入了變量i,因爲函數參數是按值傳遞的,因此會把變量i的當前值傳遞給num,這個匿名函數內部,又建立並返回了一個訪問num的閉包,這樣,最裏面的匿名函數會鎖住外層匿名函數傳遞進來的num值即當前i的值,而且返回num,這樣num就會等於此時的i的值而且賦值給數組。

4.閉包就是匿名函數嗎?

上述代碼中很容易讓人誤解:閉包就是一個匿名函數,其實否則,看下面例子:

var outName = "外面的名字";
    var later;
    function outerFunction(){
        var innerName = "裏面的名字";
        
        function innerFunction(){
            alert("I can see the "+outName);
            alert("I can see the "+innerName);
        }
        later = innerFunction;
    }
    outerFunction();
    later();

上述代碼中,在outerFunction()函數中,將內部函數innerFunction()賦值給全局變量later,執行完倒數第二步:outerFunction();以後,執行later();依然能夠能夠訪問到內部變量innerName。
由於在外部函數outerFunction中聲明innerFunction()函數時,不只聲明瞭函數,還建立了一個閉包,該閉包不只包含函數聲明,還包含了函數聲明的那一時刻該做用域中的全部變量。

5.閉包的用處

私有變量

上述例子能夠看出,閉包能夠用來封裝私有變量,就像java中的在對象內部封裝一個private私有變量同樣。
看下面例子:

function createCounter(){
        var num = 0;
        this.getNum = function(){
            return num;
        };
        this.num = function(){
            num++;
        };
    }
    
    var counter = new createCounter();
    counter.num()  //調用計數器一次,使num值加1
    //測試代碼
    alert(counter.getNum());  //1
    alert(counter.num());    //undefined

上面例子中用構造函數模式建立了一個計數器函數,而後對函數進行實例化,在構造函數內部,咱們定義了一個變量num,它的可訪問性只能在構造器內部,定義了一個getNum()方法,該方法只能對內部變量進行讀取,但不能寫入。而後又定義了方法num(),經過最後兩行測試代碼能夠看出,能夠經過存取方法getNum獲取私有變量,可是不能直接訪問私有變量。

總結:私有變量的意思是,咱們若是想對num進行加減乘除的操做,只能在createCounter內部,外部只能訪問內部進行邏輯操做後的值,而不能訪問帶有對num值進行操做的方法,這樣,咱們就能夠把本身的業務邏輯封裝在函數內部的閉包中,只須要暴露出接口讓外部獲取想要獲得的值就能夠了,也就是說主動權徹底在你定義函數時,外部只能看和獲取,而不能進行對變量值的改變的操做。

上述的目的就是建立一個用於訪問私有變量的公有方法。看下面代碼:

function Person(name){
        this.getName = function(){
            return name;
        };
        this.setName = function(){
            name = value;
        };
    }
    
    //測試
    var person = new Person("Jack");
    alert(person.getName());     //Jack
    person.setName("Rose");
    alert(person.getName());     //Rose

上面的代碼在構造函數內部定義了兩個方法:setName和getName,這兩個方法均可以在構造函數外部訪問和實用,並且都有權訪問私有變量name,但在Person構造函數外部,沒有任何辦法訪問name,因爲這兩個方法是在構造函數內部定義的,因此作爲閉包可以經過做用域鏈訪問name。上述在構造函數中定義特權方法有一個缺點,就是必需要使用構造函數模式來達到這個目的,這樣針對每一個實例都會建立一樣一組新方法。

解決辦法:靜態私有變量
看下面代碼:

(function(){
        var name = "";
        Person = function(value){
            name = value;
        };
        Person.prototype.getName = function(){
            return name;
        };
        Person.prototype.setName = function(value){
            name = value;
        };
    })();
    
    //測試函數
    var person1 = new Person("Jack");
    alert(person1.getName());         //Jack
    person1.setName("Rose");
    alert(person1.getName());         //Rose
    
    var person2 = new Person("Will");
    alert(person1.getName());          //Will
    alert(person2.getName());          //Will

上述代碼中,Person構造函數與getName()和setName()方法同樣,都有權訪問私有變量name,name變成了一個靜態的、由全部實例共享的屬性。在一個實例上調用setName會影響全部的實例。或者新建一個Person實例都會賦予name屬性一個新值。

上述兩個方法:第二種建立靜態私有變量會由於使用原型而增進代碼複用,但每一個實例都沒有本身的私有變量。

模仿塊級做用域

上面舉過例子,js沒有塊級做用域,能夠經過匿名函數當即執行把塊級做用域包裹起來,這樣就有了塊級做用域。看下面代碼:

function outputNumbers(count){
        (function(){
            for(var i = 0; i<count; i++){
                alert(i);
            }
        
        })();
        alert(i);   //此處會致使一個錯誤,顯示i是沒有定義的。
        alert(count);
    }
    //測試函數
    outputNumbers(5);

上述函數中,在for循環外部插入了一個私有做用域,在匿名函數中定義的任何變量,都會在匿名函數執行結束時被銷燬。所以變量i只能在for循環中使用,使用後即被銷燬,所以上面的代碼執行會這樣:
1.執行測試函數後,會彈出5個彈窗,會顯示0,1,2,3,4
2.執行完匿名函數後,i即被銷燬,因此執行alert(i);會報錯。
3.能夠訪問變量count,由於這個匿名函數時一個閉包,它可以訪問包含做用域中的全部變量。

這種技術常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數,看下面代碼:

(function(){
        var now = new Date();
        if(now.getMonth() == 0 && now.getDate() == 1){
            alert("元旦快樂");
        }
    })();

上述代碼放在全局做用域中,能夠用來肯定哪一天時1月1日元旦,now是匿名函數中的局部變量,不用在全局做用域中建立它。

先寫這麼多吧,之後再添加~~~~~

相關文章
相關標籤/搜索