Javascript中類的實現機制(四)


一:  理解類的實現機制

  在JavaScript中可以使用function關鍵字來定義一個“類”,如何為類添加成員。在函數內通過this指針引用的變量或者方法都會成為類的成員,例如:
function class1(){
      var s="abc";
      this.p1=s;
      this.method1=function(){
             alert("this is a test method");
      }
}
var obj1=new class1();
通過new class1()獲得對象obj1,對象obj1便自動獲得了屬性p1和方法method1。
在JavaScript中,function本身的定義就是類的構造函數,使用new創建對象的過程。
(1)當解釋器遇到new操作符時便創建一個空對象;
(2)開始運行class1這個函數,並將其中的this指針都指向這個新建的對象;
(3)因為當給對象不存在的屬性賦值時,解釋器就會為對象創建該屬性,例如在class1中,當執行到this.p1=s這條語句時,就會添加一個屬性p1,並把變量s的值賦給它,這樣函數執行就是初始化這個對象的過程,即實現構造函數的作用;
(4)當函數執行完后,new操作符就返回初始化后的對象。
通過這整個過程,JavaScript中就實現了面向對象的基本機制。由此可見,在JavaScript中,function的定義實際上就是實現一個對象的構造器,是通過函數來完成的。這種方式的缺點是:
(1)將所有的初始化語句、成員定義都放到一起,代碼邏輯不夠清晰,不易實現復雜的功能。
(2)每創建一個類的實例,都要執行一次構造函數。構造函數中定義的屬性和方法總被重復的創建,例如:
this.method1=function(){
            alert("this is a test method");
      }
這里的method1每創建一個class1的實例,都會被創建一次,造成了內存的浪費。另一種類定義的機制:prototype對象,可以解決構造函數中定義類成員帶來的缺點。

 

二:使用prototype對象定義類成員

    一種為類添加成員的機制:prototype對象。當new一個function時,該對象的成員將自動賦給所創建的對象,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
      this.prop=1;
}
//使用函數的prototype屬性給類定義新成員
class1.prototype.showProp=function(){
      alert(this.prop);
}
//創建class1的一個實例
var obj1=new class1();
//調用通過prototype原型對象定義的showProp方法
obj1.showProp();
//-->
</script>
prototype是一個JavaScript對象,可以為prototype對象添加、修改、刪除方法和屬性。從而為一個類添加成員定義。
了解了函數的prototype對象,現在再來看new的執行過程。
(1)創建一個新的對象,並讓this指針指向它;
(2)將函數的prototype對象的所有成員都賦給這個新對象;
(3)執行函數體,對這個對象進行初始化操作;
(4)返回(1)中創建的對象。
和new的執行過程相比,多了用prototype來初始化對象的過程,這也和prototype的字面意思相符,它是所對應類的實例的原型。這個初始化過程發生在函數體(構造器)執行之前,所以可以在函數體內部調用prototype中定義的屬性和方法,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
      this.prop=1;
      this.showProp();
}
//使用函數的prototype屬性給類定義新成員
class1.prototype.showProp=function(){
      alert(this.prop);
}
//創建class1的一個實例
var obj1=new class1();
//-->
</script>
和上一段代碼相比,這里在class1的內部調用了prototype中定義的方法showProp,從而在對象的構造過程中就彈出了對話框,顯示prop屬性的值為1。
需要注意,原型對象的定義必須在創建類實例的語句之前,否則它將不會起作用,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
      this.prop=1;
      this.showProp();
}
//創建class1的一個實例
var obj1=new class1();
//在創建實例的語句之后使用函數的prototype屬性給類定義新成員,只會對后面創建的對象有效
class1.prototype.showProp=function(){
      alert(this.prop);
}
//-->
</script>
這段代碼將會產生運行時錯誤,顯示對象沒有showProp方法,就是因為該方法的定義是在實例化一個類的語句之后。
由此可見,prototype對象專用於設計類的成員,它是和一個類緊密相關的,除此之外,prototype還有一個重要的屬性:constructor,表示對該構造函數的引用,例如:
function class1(){
      alert(1);
}
class1.prototype.constructor(); //調用類的構造函數
這段代碼運行后將會出現對話框,在上面顯示文字“1”,從而可以看出一個prototype是和一個類的定義緊密相關的。實際上:class1.prototype.constructor==class1。

 

三:一種JavaScript類的設計模式  

    類可以在function定義的函數體中添加成員,又可以用prototype定義類的成員,編程的代碼顯得混亂。如何以一種清晰的方式來定義類呢?下面給出了一種類的實現模式。
在JavaScript 中,由於對象靈活的性質,在構造函數中也可以為類添加成員,在增加靈活性的同時,也增加了代碼的復雜度。為了提高代碼的可讀性和開發效率,可以采用這種定義成員的方式,而使用prototype對象來替代,這樣function的定義就是類的構造函數,符合傳統意義類的實現:類名和構造函數名是相同的。例如:
function class1(){
      //構造函數
}
//成員定義
class1.prototype.someProperty="sample";
class1.prototype.someMethod=function(){
      //方法實現代碼
}
雖然上面的代碼對於類的定義已經清晰了很多,但每定義一個屬性或方法,都需要使用一次class1.prototype,不僅代碼體積變大,而且易讀性還不夠。為了進一步改進,可以使用無類型對象的構造方法來指定prototype對象,從而實現類的成員定義:
//定義一個類class1
function class1(){
      //構造函數
}
//通過指定prototype對象來實現類的成員定義
class1.prototype={
      someProperty:"sample",
      someMethod:function(){
          //方法代碼
      },
      …//其他屬性和方法.
}
上面的代碼用一種很清晰的方式定義了class1,構造函數直接用類名來實現,而成員使用無類型對象來定義,以列表的方式實現了所有屬性和方法,並且可以在定義的同時初始化屬性的值。這也更象傳統意義面向對象語言中類的實現。只是構造函數和類的成員定義被分為了兩個部分,這可看成JavaScript中定義類的一種固定模式,這樣在使用時會更加容易理解。
注意:在一個類的成員之間互相引用,必須通過this指針來進行,例如在上面例子中的 someMethod方法中,如果要使用屬性someProperty,必須通過this.someProperty的形式,因為在JavaScript 中每個屬性和方法都是獨立的,它們通過this指針聯系在一個對象上。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <script language="JavaScript" type="text/javascript">
     /function Person() {
          // console.log("這里可以寫一些類成員初始化的代碼");
          //在這進行初始化值
          this.personName = "孫麗媛";
          this.personAge = "20";
      }
      //創建類
      //先在執行這個代碼    后返回構造函數  進行賦值   最后輸出
      Person.prototype.personName = "";
      Person.prototype.personAge = 0;
      Person.prototype.showInfo = function () {
          alert(this.personName + "," + this.personAge);
      }
        var person=new Person();
        person.showInfo();

     function Person(personName,personAge) {
      // console.log("這里可以寫一些類成員初始化的代碼");
      //在這進行初始化值
      this.personName = personName;
      this.personAge = personAge;
      }
      //創建類
      //先在執行這個代碼    后返回構造函數  進行賦值   最后輸出
      Person.prototype.personName = "";
      Person.prototype.personAge = 0;
      Person.prototype.showInfo = function () {
      alert(this.personName + "," + this.personAge);
      }
      var person=new Person("sun",15);
      person.showInfo();


        function Person(personName,personAge){
            console.log("可以做一些初始化的工作");
            this.personName=personName;
            this.personAge=personAge;
        }
        Person.prototype={
            personName:"zhang",
           personAge:18,
                shouInfo:function(){
                    alert(this.personName+","+this.personAge)

                }
        };
        var p=new Person("lisi",30);
     p.shouInfo();
    </script>
</head>
<body>




</body>
</html>

 

四: 實現類的公有成員

前面定義的任何類成員都屬於公有成員的范疇,該類的任何實例都對外公開這些屬性和方法。

五:實現類的私有成員

       私有成員即在類的內部實現中可以共享的成員,不對外公開。JavaScript中並沒有特殊的機制來定義私有成員,但可以用一些技巧來實現這個功能。
        這個技巧主要是通過變量的作用域性質來實現的,在JavaScript中,一個函數內部定義的變量稱為局部變量,該變量不能夠被此函數外的程序所訪問,卻可以被函數內部定義的嵌套函數所訪問。在實現私有成員的過程中,正是利用了這一性質。
在類的構造函數中可以為類添加成員,通過這種方式定義的類成員,實際上共享了在構造函數內部定義的局部變量,這些變量就可以看作類的私有成員
例如:
<script language="JavaScript" type="text/javascript">
<!--
function class1(){
      var pp=" this is a private property"; //私有屬性成員pp
      function pm(){  //私有方法成員pm,顯示pp的值
             alert(pp);
      }
      this.method1=function(){
             //在公有成員中改變私有屬性的值
             pp="pp has been changed";
      }
      this.method2=function(){
             pm();  //在公有成員中調用私有方法
      }
}


var obj1=new  class1();
obj1.method1();  //調用公有方法method1
obj1.method2();  //調用公有方法method2
//-->
</script>

這樣,就實現了私有屬性pp和私有方法pm。運行完class1以后,盡管看上去pp和pm這些局部變量應該隨即消失,但實際上因為class1是通過new來運行的,它所屬的對象還沒消失,所以仍然可以通過公開成員來對它們進行操作。
注意:這些局部變量(私有成員),被所有在構造函數中定義的公有方法所共享,而且僅被在構造函數中定義的公有方法所共享。這意味着,在prototype中定義的類成員將不能訪問在構造體中定義的局部變量(私有成員)。
要使用私有成員,是以犧牲代碼可讀性為代價的。而且這種實現更多的是一種JavaScript技巧,因為它並不是語言本身具有的機制。但這種利用變量作用域性質的技巧,卻是值得借鑒的。

 

 

實例:  借助局部變量來模擬分裝類的私有方法:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <script language="JavaScript" type="text/javascript">
    function Person(){
        //局部變量加——
        var _age=10;
        function _changeAge(age){
            if(age<20){
                age = 20;
            }
            _age = age;
        }
        this.setAge=function(age){
            _changeAge(age);
        }
        this.getAge=function(){
            return _age;
        }
    }
        var p=new Person();
        var age= p.getAge();
        alert(age);
        p.setAge(100);
        age=p.getAge();
        alert(age);
    </script>
</head>
<body>




</body>
</html>

 

六: 實現類的靜態成員

靜態成員屬於一個類的成員,它可以通過“類名.靜態成員名”的方式訪問。在JavaScript中,可以給一個函數對象直接添加成員來實現靜態成員,因為函數也是一個對象,所以對象的相關操作,對函數同樣適用。例如:
function class1(){//構造函數
}
//靜態屬性
class1.staticProperty="sample";
//靜態方法
class1.staticMethod=function(){
      alert(class1.staticProperty);
}
//調用靜態方法
class1.staticMethod();
通過上面的代碼,就為類class1添加了一個靜態屬性和靜態方法,並且在靜態方法中引用了該類的靜態屬性。
如果要給每個函數對象都添加通用的靜態方法,還可以通過函數對象所對應的類Function來實現,例如:
//給類Function添加原型方法:show ArgsCount
Function.prototype.showArgsCount=function(){
      alert(this.length);    //顯示函數定義的形參的個數
}
function class1(a){
      //定義一個類
}
//調用通過Function的prototype定義的類的靜態方法showArgsCount
class1. showArgsCount ();
由此可見,通過Function的prototype原型對象,可以給任何函數都加上通用的靜態成員,這在實際開發中可以起到很大的作用,比如在著名的prototype-1.3.1.js框架中,就給所有的函數定義了以下兩個方法:
//將函數作為一個對象的方法運行
Function.prototype.bind = function(object) {
  var __method = this;
  return function() {
     __method.apply(object, arguments);
  }
}
//將函數作為事件監聽器
Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    __method.call(object, event || window.event);
  }
}
這兩個方法在prototype-1.3.1框架中起了很大的作用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM