函數表達式

一、定義函數

  • 函數聲明定義函數編程

    function functionName(arg0, arg1, arg2) {
      //函數體
    } 
    //只在 Firefox、Safari、Chrome 和 Opera 有效
    alert(functionName.name); //"functionName"
  • 函數聲明提高閉包

    //在執行代碼以前會先讀取函數聲明
    sayHi();
    function sayHi(){
     alert("Hi!");
    }
  • 函數表達式定義函數app

    var functionName = function(arg0, arg1, arg2){
      //函數體
    };
  • 匿名函數(拉姆達函數)函數

    //沒有函數提高
    sayHi(); //錯誤:函數還不存在
    var sayHi = function(){
      alert("Hi!");
    };
    
    //不要在if語句中使用函數聲明
    if(condition){
      function sayHi(){
          alert("Hi!");
      }
    } else {
      function sayHi(){
          alert("Yo!");
     }
    }
    //使用函數表達式定義函數
    var sayHi;
    if(condition){
      sayHi = function(){
          alert("Hi!");
      };
    } else {
      sayHi = function(){
          alert("Yo!");
      };
    }
  • 函數做爲其餘函數的值返回this

    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;
          }
      };
    }

二、遞歸

  • 遞歸函數是在一個函數經過名字調用自身的狀況下構成的prototype

    function factorial(num){
      if (num <= 1){
          return 1;
      } else {
          return num * factorial(num-1);
      }
    }
    var anotherFactorial = factorial;
    factorial = null;
    alert(anotherFactorial(4)); //出錯!
  • arguments.callee 是一個指向正在執行的函數的指針,所以能夠用它來實現對函數的遞歸調用指針

    function factorial(num){
      if (num <= 1){
          return 1;
      } else {
          return num * arguments.callee(num-1);
      }
    }

三、閉包

  • 概念:指有權訪問另外一個函數做用域中的變量的函數code

    • 建立閉包的常見方式,就是在一個函數內部建立另外一個函數component

      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;
              }
          };
      }
    • 原理:對象

      • 當某個函數被調用時,會建立一個執行環境(execution context)及相應的做用域鏈,而後,使用 arguments 和其餘命名參數的值來初始化函數的活動對象(activation object),但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,直至做爲做用域鏈終點的全局執行環境。
      • 在函數執行過程當中,爲讀取和寫入變量的值,就須要在做用域鏈中查找變量,不管何時在函數中訪問一個變量時,就會從做用域鏈中搜索具備相應名字的變量。通常來說,當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域(全局執行環境的變量對象)
      • 可是,閉包的狀況又有所不一樣,在另外一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的做用域鏈中,函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象
      • 因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,建議只在絕對必要時再考慮使用閉包
  • 閉包與變量

    • 閉包只能取得包含函數中任何變量的最後一個值

      //在每一個函數內部 i 的值都是 10
      function createFunctions(){
          var result = new Array();
          for (var i=0; i < 10; i++){
              result[i] = function(){
                  return i;
              };
          }
          return result;
      }
      createFunctions();
    • 建立另外一個匿名函數強制讓閉包的行爲符合預期:

      //每一個函數都應該返本身的索引值
      function createFunctions(){
          var result = new Array();
          for (var i=0; i < 10; i++){
              result[i] = function(num){
                  return function(){
                      return num;
                  };
              }(i);
          }
          return result;
      }
  • 關於this對象

    • 通常狀況:this 對象是在運行時基於函數的執行環境綁定的

      • 在全局函數中,this 等於 window
      • 當函數被做爲某個對象的方法調用時,this 等於那個對象
    • 匿名函數的this對象:

      • 匿名函數的執行環境具備全局性,其 this 對象一般指向 window

        var name = "The Window";
        var object = {
          name : "My Object",
          getNameFunc : function(){
              return function(){
                  return this.name;
              };
          }
        };
        alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)
      • 解決辦法:把外部做用域中的 this 對象保存在一個閉包可以訪問到的變量裏

        var name = "The Window";
        var object = {
          name : "My Object",
          getNameFunc : function(){
                var that = this;
              return function(){
                  return that.name;
              };
          }
        };
        alert(object.getNameFunc()()); //"My Object"
    • this 的值可能會意外地改變的特殊狀況:

      var name = "The Window";
      var object = {
          name : "My Object",
          getName: function(){
              return this.name;
          }
      }; 
      object.getName(); //"My Object"
      (object.getName)(); //"My Object"
      (object.getName = object.getName)(); //"The Window",在非嚴格模式下
  • 內存泄漏

    • 閉包的做用域鏈中保存着一個HTML 元素,那麼就意味着該元素將沒法被銷燬

      function assignHandler(){
          var element = document.getElementById("someElement");
          element.onclick = function(){
              alert(element.id);
          };
      }
    • 解決辦法:

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

四、模仿塊級做用域

  • JavaScript 沒有塊級做用域:在塊語句中定義的變量,其實是在包含函數中而非語句中建立的

    function outputNumbers(count){
      for (var i=0; i < count; i++){
          alert(i);
      }
        var i; //從新聲明變量,也不會改變它的值
      alert(i); //計數
    }
  • 使用自調用函數產生塊級做用域(一般稱爲私有做用域)

    function outputNumbers(count){
      (function () {
          for (var i=0; i < count; i++){
              alert(i);
          }
      })();
      alert(i); //致使一個錯誤!
    }
  • 這種技術常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數。這種作法能夠減小閉包占用的內存問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈

    (function(){
      var now = new Date();
      if (now.getMonth() == 0 && now.getDate() == 1){
          alert("Happy new year!");
      }
    })();

五、私有變量

  • 基本概念:

    • 嚴格來說,JavaScript 中沒有私有成員的概念;全部對象屬性都是公有的,不過,卻是有一個私有變量的概念。任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數的外部訪問這些變量。私有變量包括函數的參數、局部變量和在函數內部定義的其餘函數。

      function add(num1, num2){
          var sum = num1 + num2;
          return sum;
      }
    • 特權方法:有權訪問私有變量和私有函數的公有方法

    • 建立特權方法的方式之一:在構造函數中定義特權方法

      • 基本模式:

        function MyObject(){
          //私有變量和私有函數
          var privateVariable = 10;
          function privateFunction(){
              return false;
          }
          //特權方法
          this.publicMethod = function (){
          privateVariable++;
              return privateFunction();
          };
        }
      • 利用私有和特權成員,能夠隱藏那些不該該被直接修改的數據

        function Person(name){
          this.getName = function(){
              return name;
          };
          this.setName = function (value) {
              name = value;
          };
        }
        var person = new Person("Nicholas");
        alert(person.getName()); //"Nicholas"
        person.setName("Greg");
        alert(person.getName()); //"Greg"
      • 缺點:必須使用構造函數模式來達到這個目的,構造函數模式的缺點是針對每一個實例都會建立一樣一組新方法,而使用靜態私有變量來實現特權方法就能夠避免這個問題

    • 建立特權方法的方式之二:靜態私有變量(見下)

  • 靜態私有變量

    • 基本模式

      (function(){
          //私有變量和私有函數
          var privateVariable = 10;
          function privateFunction(){
              return false;
          }
          //構造函數
          MyObject = function(){};
          //公有/特權方法
           MyObject.prototype.publicMethod = function(){
              privateVariable++;
              return privateFunction();
          };
      })();
    • 與在構造函數中定義特權方法的主要區別:私有變量和函數是由實例共享的。因爲特權方法是在原型上定義的,所以全部實例都使用同一個函數。而這個特權方法,做爲一個閉包,老是保存着對包含做用域的引用。

      (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("Nicholas");
      alert(person1.getName()); //"Nicholas"
      person1.setName("Greg");
      alert(person1.getName()); //"Greg"
      var person2 = new Person("Michael");
      alert(person1.getName()); //"Michael"
      alert(person2.getName()); //"Michael"
    • 優勢:以這種方式建立靜態私有變量會由於使用原型而增進代碼複用,但每一個實例都沒有本身的私有變
      量。多查找做用域鏈中的一個層次,就會在必定程度上影響查找速度。而這正是使用閉包和私有變量的一個顯明的不足之處

  • 模塊模式

    • 單例:指的就是隻有一個實例的對象

      var singleton = {
          name : value,
          method : function () {
          //這裏是方法的代碼
          }   
      };
    • 模塊模式:爲單例建立私有變量和特權方法

      var singleton = function(){
          //私有變量和私有函數
          var privateVariable = 10;
          function privateFunction(){
              return false;
          } //特權/公有方法和屬性
          return {
              publicProperty: true,
              publicMethod : function(){
                  privateVariable++;
                  return privateFunction();
              }
          };
      }();
    • 從本質上來說,這個對象字面量定義的是單例的公共接口。這種模式在須要對單例進行某些初始化,同時又須要維護其私有變量時是很是有用的

      var application = function () {
          //私有變量和函數
          var components = new Array();
          //初始化
          components.push(new BaseComponent());
          //公共
          return {
              getComponentCount: function () {
                  return components.length;
              },
              registerComponent: function (component) {
                  if (typeof component == "object") {
                      components.push(component);
                  }
              }
          };
      }();
    • 若是必須建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。以這種模式建立的每一個單例都是 Object 的實例,由於最終要經過一個對象字面量來表示它。事實上,這也沒有什麼;畢竟,單例一般都是做爲全局對象存在的,咱們不會將它傳遞給一個函數。所以,也就沒有什麼必要使用instanceof操做符來檢查其對象類型了

  • 加強的模塊模式

    • 加強:在返回對象以前加入對其加強的代碼的,這種加強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性和(或)方法對其加以加強的狀況

      var singleton = function(){
          //私有變量和私有函數
          var privateVariable = 10;
          function privateFunction(){
              return false;
          }
          //建立對象
          var object = new CustomType();
          //添加特權/公有屬性和方法
          object.publicProperty = true;
          object.publicMethod = function(){
              privateVariable++;
              return privateFunction();
          };
          //返回這個對象
          return object;
      }();
    • 或者這樣寫:

      var application = function(){
          //私有變量和函數
          var components = new Array();
          //初始化
          components.push(new BaseComponent());
          //建立 application 的一個局部副本
          var app = new BaseComponent();
          //公共接口
          app.getComponentCount = function(){
              return components.length;
          };
          app.registerComponent = function(component){
              if (typeof component == "object"){
                  components.push(component);
              }
          };
          //返回這個副本
          return app;
      }();

六、總結

一、在 JavaScript 編程中,函數表達式是一種很是有用的技術。使用函數表達式能夠無須對函數命名,從而實現動態編程。匿名函數,也稱爲拉姆達函數,是一種使用 JavaScript 函數的強大方式。如下總結了函數表達式的特色:

  • 函數表達式不一樣於函數聲明。函數聲明要求有名字,但函數表達式不須要。沒有名字的函數表達式也叫作匿名函數;
  • 在沒法肯定如何引用函數的狀況下,遞歸函數就會變得比較複雜;
  • 遞歸函數應該始終使用 arguments.callee 來遞歸地調用自身,不要使用函數名——函數名可能會發生變化

二、當在函數內部定義了其餘函數時,就建立了閉包。閉包有權訪問包含函數內部的全部變量,原理以下:

  • 在後臺執行環境中,閉包的做用域鏈包含着它本身的做用域、包含函數的做用域和全局做用域;
  • 一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬;
  • 可是,當函數返回了一個閉包時,這個函數的做用域將會一直在內存中保存到閉包不存在爲止;

三、使用閉包能夠在 JavaScript 中模仿塊級做用域(JavaScript 自己沒有塊級做用域的概念),要點以下:

  • 建立並當即調用一個函數,這樣既能夠執行其中的代碼,又不會在內存中留下對該函數的引用;
  • 結果就是函數內部的全部變量都會被當即銷燬——除非將某些變量賦值給了包含做用域(即外部做用域)中的變量;

四、閉包還能夠用於在對象中建立私有變量,相關概念和要點以下:

  • 即便 JavaScript 中沒有正式的私有對象屬性的概念,但可使用閉包來實現公有方法,而經過公有方法能夠訪問在包含做用域中定義的變量;
  • 有權訪問私有變量的公有方法叫作特權方法;
  • 可使用構造函數模式、原型模式來實現自定義類型的特權方法,也可使用模塊模式、加強的模塊模式來實現單例的特權方法;

五、JavaScript 中的函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。不過,由於建立閉包必須維護額外的做用域,因此過分使用它們可能會佔用大量內存

相關文章
相關標籤/搜索