首頁 >web前端 >js教程 >學習JavaScript設計模式(封裝)_javascript技巧

學習JavaScript設計模式(封裝)_javascript技巧

WBOY
WBOY原創
2016-05-16 15:29:421043瀏覽

在JavaScript 中,並沒有對抽象類別和介面的支援。 JavaScript 本身也是一門弱型別語言。在封裝類型方面,JavaScript 沒有能力,也沒有必要做得更多。對於JavaScript 的設計模式實作來說,不區分型別是一種失色,也可以說是一種解脫。

從設計模式的角度出發,封裝在更重要的層面體現為封裝變化。

透過封裝變化的方式,把系統中穩定不變的部分和容易變化的部分隔離開來,在系統的演變過程中,我們只需要替換那些容易變化的部分,如果這些部分是已經封裝好的,替換起來也相對容易。這可以最大程度地保證程式的穩定性和可擴展性。

javascript封裝的的基本模式有3種:

1、使用約定優先的原則,將所有的私有變數以_開頭

 <script type="text/javascript">
  /**
   * 使用约定优先的原则,把所有的私有变量都使用_开头
   */
  var Person = function (no, name, age)
  {
   this.setNo(no);
   this.setName(name);
   this.setAge(age);
  }
  Person.prototype = {
   constructor: Person,
   checkNo: function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   },
   setNo: function (no)
   {
    this.checkNo(no);
    this._no = no;
   }, 
   getNo: function ()
   {
    return this._no;
   setName: function (name)
   {
    this._name = name;
   }, 
   getName: function ()
   {
    return this._name;
   }, 
   setAge: function (age)
   {
    this._age = age;
   }, 
   getAge: function ()
   {
    return this._age;
   }, 
   toString: function ()
   {
    return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
   }
  };
  var p1 = new Person("0001", "小平果", "22");
  console.log(p1.toString());  //no = 0001 , name = 小平果 , age = 22
  p1.setNo("0003");
  console.log(p1.toString());  //no = 0003 , name = 小平果 , age = 22
  p1.no = "0004";
  p1._no = "0004";
  console.log(p1.toString()); //no = 0004 , name =小平果 , age = 22

 </script>

看完程式碼,是不是有種被坑的感覺,僅僅把所有的變數以_開頭,其實還是可以直接存取的,這能叫封裝麼,當然了,說了是約定優先嘛。

下劃線的這種用法這一個眾所周知的命名規範,它表明一個屬性僅供對象內部使用,直接訪問它或設置它可能會導致意想不到的後果。這有助於防止程式設計師對它的無意使用,卻不能防止對它的有意使用。

這種方式還是不錯的,最起碼成員變數的getter,setter方法都是prototype中,並非存在物件中,整體來說還是個不錯的選擇。如果你覺得,這不行,必須嚴格實現封裝,那麼看第二種方式。

2、嚴格實現封裝

<script type="text/javascript">
  /**
   * 使用这种方式虽然可以严格实现封装,但是带来的问题是get和set方法都不能存储在prototype中,都是存储在对象中的
   * 这样无形中就增加了开销
   */
  var Person = function (no, name, age)
  {
   var _no , _name, _age ;
   var checkNo = function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   };
   this.setNo = function (no)
   {
    checkNo(no);
    _no = no;
   };
   this.getNo = function ()
   {
    return _no;
   }
   this.setName = function (name)
   {
    _name = name;
   }

   this.getName = function ()
   {
    return _name;
   }

   this.setAge = function (age)
   {
    _age = age;
   }
   this.
     getAge = function ()
   {
    return _age;
   }

   this.setNo(no);
   this.setName(name);
   this.setAge(age);
  }
  Person.prototype = {
   constructor: Person,
   toString: function ()
   {
    return "no = " + this.getNo() + " , name = " + this.getName() + " , age = " + this.getAge();
   }
  }
  ;
  var p1 = new Person("0001", "小平果", "22");
  console.log(p1.toString());  //no = 0001 , name =小平果 , age = 22
  p1.setNo("0003");
  console.log(p1.toString());  //no = 0003 , name = 小平果 , age = 22
  p1.no = "0004";
  console.log(p1.toString()); //no = 0003 , name = 小平果 , age = 22

 </script>

那麼這與我們先前講過的其他創建對象的模式有什麼不同呢,在上面的例子中,我們在創建和引用對象的屬性時總是要使用this關鍵字。而在本例中,我們用var宣告這些變數。這意味著它們只存在於Person構造器中。 checkno函數也是用同樣的方式宣告的,因此成了一個私用方法。

需要存取這些變數和函數的方法只需要聲明在Person中即可。這些方法被稱為特權方法,因為它們是公用方法,但卻能夠存取私人屬性和方法。為了在物件外部能存取這些特權函數,它們的前面被加上了關鍵字this。因為這些方法定義於Person建構器的作用域,所以它們能存取到私用屬性。引用這些屬性時並沒有使用this關鍵字,因為它們不是公開的。所有取值器和賦值器方法都改為不加this地直接引用這些屬性。

任何不需要直接存取的私人屬性的方法都可以像原來那樣在Person.prototype中聲明。像toString()方法。只有那些需要直接存取私人成員的方法才應該被設計為特權方法。但特權方法太多又會佔用過多的內存,因為每個物件實例都包含所有特權方法的新副本。

看上面的程式碼,去掉了this.屬性名,嚴格的實作了封裝,只能透過getter,setter存取成員變數了,但是存在一個問題,所有的方法都存在物件中,增加了記憶體的開銷。

3、以閉包的方式封裝

<script type="text/javascript">

  var Person = (function ()
  {
   //静态方法(共享方法)
   var checkNo = function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   };
   //静态变量(共享变量)
   var times = 0;

    //return the constructor.
   return function (no, name, age)
   {
    console.log(times++); // 0 ,1 , 2
    var no , name , age; //私有变量
    this.setNo = function (no) //私有方法
    {
     checkNo(no);
     this._no = no;
    };
    this.getNo = function ()
    {
     return this._no;
    }
    this.setName = function (name)
    {
     this._name = name;
    }

    this.getName = function ()
    {
     return this._name;
    }

    this.setAge = function (age)
    {
     this._age = age;
    }
    this.getAge = function ()
    {
     return this._age;
    }

    this.setNo(no);
    this.setName(name);
    this.setAge(age);
   }
  })();

  Person.prototype = {
   constructor: Person,
   toString: function ()
   {
    return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
   }
  };

  var p1 = new Person("0001", "小平果", "22");
  var p2 = new Person("0002", "abc", "23");
  var p3 = new Person("0003", "aobama", "24");


  console.log(p1.toString());  //no = 0001 , name = 小平果 , age = 22
  console.log(p2.toString());  //no = 0002 , name = abc , age = 23
  console.log(p3.toString()); //no = 0003 , name = aobama , age = 24

 </script>

上述程式碼,js引擎載入完後,會直接執行Person = 立即執行函數,然後此函數傳回了一個子函數,這個子函數才是new Person所呼叫的建構函數,又因為子函數中保持了對立即執行函數中checkNo(no) ,times的引用,(很明顯的閉包)所以對於checkNo和times,是所有Person對象所共有的,創建3個對象後,times分別為0,1,2 。這種方式的好處是,可以使Person中需要重複使用的方法和屬性做到私有且物件間共用。

這裡的私人成員特權成員仍然被聲明在構造器中。但那個建構器卻從原來的普通函數變成了一個內嵌函數,並且被當作包含它的函數的回傳值給變數Person。這就創建了一個閉包,你可以把靜態的私人成員聲明在裡面。位於外層函數宣告之後的一對空括號很重要,其作用是程式碼一載入就會立即執行這個函數。這個函數的回傳值是另一個函數,它被賦給Person變量,Person因此成了一個建構函數。在實例華Person時,所呼叫的這個內層函數。外層那個函數只是用來建立一個可以用來儲存靜態成員的閉包。

在本例中,checkno被設計成為靜態方法,原因是為Person的每個實例都產生這個方法的一個新副本毫無道理。另外還有靜態屬性times,其作用在於追蹤Person建構器的總呼叫次數

以上就是本文的全部內容,希望對大家的學習有所幫助,大家可以更深入的學習了解封裝的意義。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn