JavaScript高級程序設計筆記-函數表達式

定義函數的方式:

1.函數聲明

  1. function functionName(arg0,arg1,arg2){    //函數體    }
       特徵:函數聲明提前,在執行代碼之前會讀取函數聲明,意味着可以把函數聲明放在調用它的語句後面。

2.函數表達式

  1. var funtionName = function(arg0,arg1,arg2){    //函數體    }
       匿名函數,創建一個函數並將它賦值給變量functionName,在使用之前必須賦值。

匿名函數的用途:

1.遞歸

        遞歸函數是在一個函數通過名字調用自身的情況下構成的。
  1. funtion factorial(num){
  2.     if(num <=1){ return 1}
  3.     else return{ num * factorial(num-1) }
  4. }
  5. var anotherFactorial = factorial;
  6. factorial = null;
  7. alert(anotherFactorial(4));//出錯,factorial不再是函數
       arguments.callee是一個指向正在執行的函數的指針,可以實現遞歸調用:
  1. return num * arguments.callee(num-1);
        但在嚴格模式下,不能通過腳本訪問arguments.callee,可以使用命名函數表達式
  1. var factorial = (function f(num){
  2.     if(num <=1) return 1;
  3.     else return num*f(num-1);
  4. })

2.閉包

        閉包是指有權訪問另一個函數作用域中的變量的函數,創建閉包的常見方法,就是在一個函數內部創建另一個函數。
  1. function createComparisonFuncion(propertyName){
  2.     return function(object1,object2){
  3.         var value1 = object1[propertyName];
  4.         var value2 = object2[propertyName];
  5.         if(value1 < value2)    return -1;
  6.         else if(value1 > value2) return 1;
  7.         else    return 0;
  8.    }
  9. }
        函數如何被調用:當某個函數被調用時,會創建一個執行環境及相應的作用域鏈,然後使用arguments和其他命名參數的值來初始化函數的活動對象,但在作用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,直至作爲作用域鏈終點的全局執行環境。
      如何構建作用域鏈:在創建函數時,會創建一個預先包含全局變量對象(表示變量的對象)的作用域鏈,這個作用域鏈被保存在內部的[[scope]]屬性中,在調用函數時,會爲函數創建一個執行環境,通過複製函數的[[scope]]屬性中的對象構建起執行環境的作用域鏈,此後,又有一個活動對象被創建並被推入執行環境作用域鏈的前端。
       作用域鏈的本質是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。
        當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局作用域(全局執行環境的變量對象),閉包則在另一個函數內部定義的函數會將包含外部函數的活動對象添加到它的作用域鏈中。
        注:由於閉包會攜帶包含它的函數的作用域,因此會比其他函數佔用更多的內存。過度使用閉包可能會導致內存佔用過多

2.1閉包與變量

        閉包只能取得包含函數中任何變量的最後一個值。
  1. function createFunctions(){
  2.    var result = new Array();
  3.    for (var i=0; i < 10; i++){
  4.        result[i] = function(){
  5.           return i;
  6.        };
  7.    }
  8.    return result;//每個函數都返回10
  9. }
        每個函數的作用域鏈中都保存着createFunction()函數活動對象,所以它們引用的都是同一個變量i,當函數返回後,變量i的值是10,此時每個函數都引用着保存變量i的同一個變量對象,所以每個函數內部i的值都是10。
        解決方法:可以通過創建另一個匿名函數強制讓閉包的行爲符合預期:
  1. function createFunctions(){
  2.    var result = new Array();
  3.    for (var i=0; i < 10; i++){
  4.        result[i] = function(num){//result數組中的每個函數都有自己num變量的一個副本
  5.            return function(){
  6.                return num;
  7.            };
  8.        }(i);
  9.    }
  10.    return result;
  11. }

2.2關於this對象

       this 對象是在運行時基於函數的執行環境綁定的:在全局函數中, this 等於window,而當函數被作爲某個對象的方法調用時, this 等於那個對象。不過,匿名函數的執行環境具有全局性,因此其 this 對象通常指向 window
        解決方法:把外部作用域中的this對象保存在一個閉包能夠訪問到的變量裏,就可以讓閉包訪問該對象了。
  1. var name = "The Window";
  2. var object = {
  3.    name : "My Object",
  4.    getNameFunc : function(){
  5.        var that = this;
  6.        return function(){
  7.            return that.name;
  8.        };
  9.    }
  10. };
  11. alert(object.getNameFunc()()); //"My Object"

2.3內存泄漏

        閉包在IE中會導致:如果閉包的作用域鏈中保存着一個HTML元素,那麼就意味着該元素將無法被銷燬。
  1. function assignHandler(){
  2.    var element = document.getElementById("someElement");
  3.    element.onclick = function(){//無法減少element的引用數,只要匿名函數存在,element的引用數至少爲1,它所佔用的內存永遠不會被回收
  4.        alert(element.id);
  5.    };
  6. }  
       解決方法:將element.id的一個副本保存在一個變量中,並且在閉包中引用該變量消除了循環引用。閉包會引用包含函數的整個活動對象,而其中包含着 element。即使閉包不直接引用 element,包含函數的活動對象中也仍然會保存一個引用。因此,有必要把 element 變量設置爲 null。這樣就能夠解除對 DOM 對象的引用,順利地減少其引用數,確保正常回收其佔用的內存。  
  1. function assignHandler(){
  2.    var element = document.getElementById("someElement");
  3.    var id = element.id;
  4.    element.onclick = function(){
  5.        alert(id);
  6.    };
  7.    element = null;
  8. }

3.模仿塊級作用域

        在塊語句中定義的變量,實際上是在包含函數中而非語句中創建的。
        沒有塊級作用域的問題:在語句塊中定義的變量在函數內部隨處可以訪問,即使錯誤地重新聲明一個變量,也不會改變它的值。
  1. function outputNumbers(count){
  2.    for (var i=0; i < count; i++){
  3.        alert(i);
  4.    }
  5.    alert(i); //計數
  6. }
        解決辦法:使用匿名函數模塊塊級作用域(私有作用域),在匿名函數中定義任何變量,都會在執行結束時被銷燬,語法如下。function關鍵字當作一個函數聲明的開始,要將函數聲明轉換成函數表達式
  1. (function(){
  2.    //塊級作用域
  3. })()
  1. function outputNumbers(count){
  2.    (function () {
  3.        for (var i=0; i < count; i++){
  4.            alert(i);
  5.        }
  6.    })();
  7.    alert(i); //導致一個錯誤!
  8. }

4.私有變量

        在函數內部創建一個閉包,那麼閉包通過自己的作用域鏈可以訪問變量,就可以創建用於訪問私有變量和私有函數的公有方法(特權方法)。
       方式一:在構造函數中定義特權方法,利用私有和特權成員,可以隱藏不應該被直接修改的數據
  1. function MyObject(){
  2.     var privateVariable = 10;
  3.     function privateFunction(){
  4.         return false;
  5.    }
  6.     //特權方法
  7.     this.publicMethod = function(){
  8.         privateVariable++;
  9.         return privateFunction();
  10.    }//在創建MyObject的實例後,除了使用publicMethod這一個途徑外,沒有任何辦法可以直接訪問privateVariable和privateFunction
  11. }

4.1靜態私有變量

      方式二:通過在私有作用域中定義私有變量或函數
  1. (function(){
  2.    //私有變量和私有函數
  3.    var privateVariable = 10;
  4.    function privateFunction(){
  5.        return false;
  6.    }
  7.    //構造函數,使用了函數表達式(函數聲明只能創建局部函數)
  8.    MyObject = function(){
  9.    };//初始化未經聲明的變量,總會創建一個全局變量,所以MyObject是全局變量,能夠在私有作用域之外被訪問到
  10.    //公有/特權方法
  11.    MyObject.prototype.publicMethod = function(){
  12.        privateVariable++;
  13.        return privateFunction();
  14.    };
  15. })();  
        與方式一的區別:私有變量和函數是由實例共享的。由於特權方法是在原型上定義的,因此所有實例都使用同一個函數。而這個特權方法,作爲一個閉包,總是保存着對包含作用域的引用。
        對象的方法可以直接調用,在原型鏈上的方法需要實例化纔可以調用。
  1. (function(){
  2.    var name = "";
  3.    Person = function(value){
  4.        name = value;
  5.    };
  6.    Person.prototype.getName = function(){
  7.        return name;
  8.    };
  9.    Person.prototype.setName = function (value){      
  10.        name = value;
  11.    };
  12. })();
  13. var person1 = new Person("Nicholas");
  14. alert(person1.getName()); //"Nicholas"
  15. person1.setName("Greg");
  16. alert(person1.getName()); //"Greg"
  17. var person2 = new Person("Michael");
  18. alert(person1.getName()); //"Michael"
  19. alert(person2.getName()); //"Michael"  
  20. //在一個實例上調用 setName()會影響所有實例。而調用 setName()或新建一個 Person 實例都會賦予 name 屬性一個新值。結果就是所有實例都會返回相同的值

4.2模塊模式

       爲單例創建私有變量和特權方法。所謂單例(singleton),指的就是隻有一個實例的對象。按照慣例, JavaScript 是以對象字面量的方式來創建單例對象的。
   以對象字面量的方式來創建單例對象:
  1. var singleton = {
  2.    name : value,
  3.    method : function () {
  4.       //這裏是方法的代碼
  5.    }
  6. }; 
       模塊模式通過爲單例添加私有變量和特權方法能夠使其得到增強,其語法形式如下:
  1. var singleton = function(){
  2.    //私有變量和私有函數
  3.    var privateVariable = 10;
  4.    function privateFunction(){
  5.        return false;
  6.    }  
  7.    //特權/公有方法和屬性
  8.    return {//匿名函數
  9.        publicProperty: true,
  10.        publicMethod : function(){
  11.            privateVariable++;
  12.            return privateFunction();
  13.        }
  14.    };
  15. }();
       這個對象字面量定義的是單例的公共接口。這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的,例如:
  1. var application = function(){//管理組件的對象
  2.    //私有變量和函數
  3.    var components = new Array();
  4.    //初始化
  5.    components.push(new BaseComponent());
  6.    //有權訪問數組components的特權方法
  7.    return {
  8.        getComponentCount : function(){
  9.            return components.length;
  10.       },
  11.        registerComponent : function(component){
  12.            if (typeof component == "object"){
  13.                components.push(component);
  14.            }
  15.        }
  16.    };
  17. }();
       如果必須創建一個對象並以某些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法,那麼就可以使用模塊模式。

4.3增強的模塊模式

       改進:在返回對象之前加入對其增強的代碼,適合單例必須是某種類型的實例,同時還必須添加某些屬性或方法對其加以增強的情況。
  1. var singleton = function(){
  2.    //私有變量和私有函數
  3.    var privateVariable = 10;
  4.    function privateFunction(){
  5.         return false;
  6.    }
  7.    //創建對象
  8.    var object = new CustomType();
  9.    //添加特權/公有屬性和方法
  10.    object.publicProperty = true;
  11.    object.publicMethod = function(){
  12.        privateVariable++;
  13.        return privateFunction();
  14.    };
  15.    //返回這個對象
  16.    return object;
  17. }();
       如果前面演示模塊模式的例子中的 application 對象必須是 BaseComponent 的實例,那麼就可以使用以下代碼。
  1. var application = function(){
  2.    //私有變量和函數
  3.    var components = new Array();
  4.    //初始化
  5.    components.push(new BaseComponent());
  6.    //創建 application 的一個局部副本
  7.    var app = new BaseComponent();
  8.    //公共接口
  9.    app.getComponentCount = function(){
  10.        return components.length;
  11.    };
  12.    app.registerComponent = function(component){
  13.        if (typeof component == "object"){
  14.            components.push(component);
  15.        }
  16.    };
  17.    //返回這個副本
  18.    return app;
  19. }();

5.小結

       在 JavaScript 編程中,函數表達式是一種非常有用的技術。使用函數表達式可以無須對函數命名,從而實現動態編程。匿名函數,也稱爲拉姆達函數,是一種使用 JavaScript 函數的強大方式。以下總結函數表達式的特點
  • 函數表達式不同於函數聲明。函數聲明要求有名字,但函數表達式不需要。沒有名字的函數表達式也叫做匿名函數。
  • 在無法確定如何引用函數的情況下,遞歸函數就會變得比較複雜;
  • 遞歸函數應該始終使用 arguments.callee 來遞歸地調用自身,不要使用函數名——函數名可能會發生變化。
       當在函數內部定義了其他函數時,就創建了閉包。閉包有權訪問包含函數內部的所有變量,原理如下:
  1. 在後臺執行環境中,閉包的作用域鏈包含着它自己的作用域、包含函數的作用域和全局作用域。
  2. 通常,函數的作用域及其所有變量都會在函數執行結束後被銷燬。
  3. 但是,當函數返回了一個閉包時,這個函數的作用域將會一直在內存中保存到閉包不存在爲止。
        使用閉包可以在 JavaScript 中模仿塊級作用域(JavaScript 本身沒有塊級作用域的概念),要點如下:
  1. 創建並立即調用一個函數,這樣既可以執行其中的代碼,又不會在內存中留下對該函數的引用。
  2. 結果就是函數內部的所有變量都會被立即銷燬——除非將某些變量賦值給了包含作用域(即外部作用域)中的變量。
       閉包還可以用於在對象中創建私有變量,相關概念和要點如下:
  1. 即使 JavaScript 中沒有正式的私有對象屬性的概念,但可以使用閉包來實現公有方法,而通過公有方法可以訪問在包含作用域中定義的變量。
  2. 有權訪問私有變量的公有方法叫做特權方法。
  3. 可以使用構造函數模式、原型模式來實現自定義類型的特權方法,也可以使用模塊模式、增強的模塊模式來實現單例的特權方法。
       JavaScript 中的函數表達式和閉包都是極其有用的特性,利用它們可以實現很多功能。不過,因爲創建閉包必須維護額外的作用域,所以過度使用它們可能會佔用大量內存。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章