天天看點

Javascript中的反射機制(五)

一: 什麼是反射機制

      反射機制指的是程式在運作時能夠擷取自身的資訊。例如一個對象能夠在運作時知道自己有哪些方法和屬性。

二: 在JavaScript中利用for(…in…)語句實作反射

在JavaScript中有一個很友善的文法來實作反射,即for(…in…)語句,其文法如下:

for(var p in obj){

      //語句

}

這裡var p表示聲明的一個變量,用以存儲對象obj的屬性(方法)名稱,有了對象名和屬性(方法)名,就可以使用方括号文法來調用一個對象的屬性(方法):

for(var p in obj){

      if(typeof(obj[p])=="function"){

             obj[p]();

      }else{

             alert(obj[p]);

      }

}

這段語句周遊obj對象的所有屬性和方法,遇到屬性則彈出它的值,遇到方法則立刻執行。在面向對象的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(){
       this.personName="孫麗媛";
       this.personAge =18;
       this.showInfo=function(){
           alert(this.personName+ "," + this.personAge);
       }
   }
   

        function show(){
            var obj=new Person();
            for(var o in obj)
            {
                if(typeof(obj[o])=="function"){
                    var isOk=confirm(o+"是一個方法,是否需要檢視源碼");
                    if(isOk){
                        alert(obj[o]);
                    }
                }else{
                    alert(o);
                }
            }
        }
    </script>
</head>
<body>

<button onclick="show()">test</button>


</body>
</html>
      

 三: 使用反射來傳遞樣式參數

在Ajax程式設計中,經常要能動态的改變界面元素的樣式,這可以通過對象的style屬性來改變,比如要改變背景色為紅色,可以這樣寫:

element.style.backgroundColor="#ff0000";

其中style對象有很多屬性,基本上CSS裡擁有的屬性在JavaScript中都能夠使用。如果一個函數接收參數用用指定一個界面元素的樣式,顯然一個或幾個參數是不能符合要求的,下面是一種實作:

function setStyle(_style){

      //得到要改變樣式的界面對象

      var element=getElement();

      element.style=_style;

}

這樣,直接将整個style對象作為參數傳遞了進來,一個style對象可能的形式是:

var style={

      color:#ffffff,

      backgroundColor:#ff0000,

      borderWidth:2px

}

這時可以這樣調用函數:

setStyle(style);

或者直接寫為:

setStyle({ color:#ffffff,backgroundColor:#ff0000,borderWidth:2px});

這段代碼看上去沒有任何問題,但實際上,在setStyle函數内部使用參數_style為element.style指派時,如果element原先已經有了一定的樣式,例如曾經執行過:

element.style.height="20px";

而_style中卻沒有包括對height的定義,是以element的height樣式就丢失了,不是最初所要的結果。要解決這個問題,可以用反射機制來重寫setStyle函數:

function setStyle(_style){

      //得到要改變樣式的界面對象

      var element=getElement();

      for(var p in _style){

            element.style[p]=_style[p];

      }

}

程式中周遊_style的每個屬性,得到屬性名稱,然後再使用方括号文法将element.style中的對應的屬性指派為_style中的相應屬性的值。進而,element中僅改變指定的樣式,而其他樣式不會改變,得到了所要的結果。

<!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 changeDivStyle() {
            var oDiv = document.getElementById("mydiv");
            var oStyle = {
                backgroundColor: "#ff0000",
                color: "#ffffff"
            }
            if (oDiv) {
                for (var o in oStyle) {
                    oDiv.style[o] = oStyle[o];
                }
            }
        }
    </script>
</head>
<body>

<div id="mydiv" style="width:200px;height: 200px">
sunliyuan
</div>
<button onclick=\'changeDivStyle({backgroundColor:"#ff0000",color:"#ffffff","text-align":"center"})\'>ChangeDivStyle</button>

</body>
</html>
      

  四:利用共享prototype實作繼承

   繼承是面向對象開發的又一個重要概念,它可以将現實生活的概念對應到程式邏輯中。例如水果是一個類,具有一些公共的性質;而蘋果也是一類,但它們屬于水果,是以蘋果應該繼承于水果。

在JavaScript中沒有專門的機制來實作類的繼承,但可以通過拷貝一個類的prototype到另外一個類來實作繼承。一種簡單的實作如下:

fucntion class1(){

      //構造函數

}

function class2(){

      //構造函數

}

class2.prototype=class1.prototype;

class2.prototype.moreProperty1="xxx";

class2.prototype.moreMethod1=function(){

      //方法實作代碼

}

var obj=new class2();

這樣,首先是class2具有了和class1一樣的prototype,不考慮構造函數,兩個類是等價的。随後,又通過prototype給class2賦予了兩個額外的方法。是以class2是在class1的基礎上增加了屬性和方法,這就實作了類的繼承。

JavaScript提供了instanceof操作符來判斷一個對象是否是某個類的執行個體,對于上面建立的obj對象,下面兩條語句都是成立的:

obj instanceof class1

obj instanceof class2

表面上看,上面的實作完全可行,JavaScript也能夠正确的了解這種繼承關系,obj同時是class1和class2的執行個體。事是上不對, JavaScript的這種了解實際上是基于一種很簡單的政策。先使用prototype讓class2繼承于class1,再在 class2中重複定義method方法:

<script language="JavaScript" type="text/javascript">

<!--

//定義class1

function class1(){

      //構造函數

}

//定義class1的成員

class1.prototype={

      method:function(){

            alert(1);

      }

}

//定義class2

function class2(){

      //構造函數

}

//讓class2繼承于class1

class2.prototype=class1.prototype;

//給class2重複定義方法method

class2.prototype.method=function(){

      alert(2);

}

//建立兩個類的執行個體

var obj1=new class1();

var obj2=new class2();

//分别調用兩個對象的method方法

obj1.method();

obj2.method();

//-->

</script>

從代碼執行結果看,彈出了兩次對話框“2”,當對class2進行prototype的改變時,class1的prototype也随之改變,即使對class2的prototype增減一些成員,class1的成員也随之改變。是以class1和class2僅僅是構造函數不同的兩個類,它們保持着相同的成員定義。class1和class2的prototype是完全相同的,是對同一個對象的引用。其實從這條指派語句就可以看出來:

//讓class2繼承于class1

class2.prototype=class1.prototype;

在JavaScript 中,除了基本的資料類型(數字、字元串、布爾等),所有的指派以及函數參數都是引用傳遞,而不是值傳遞。是以上面的語句僅僅是讓class2的 prototype對象引用class1的prototype,造成了類成員定義始終保持一緻的效果。從這裡也看到了instanceof操作符的執行機制,它就是判斷一個對象是否是一個prototype的執行個體,因為這裡的obj1和obj2都是對應于同一個prototype,是以它們 instanceof的結果都是相同的。

是以,使用prototype引用拷貝實作繼承不是一種正确的辦法。但在要求不嚴格的情況下,卻也是一種合理的方法,惟一的限制是不允許類成員的覆寫定義。

五: 利用反射機制和prototype實作繼承

     共享prototype來實作類的繼承,不是一種很好的方法,畢竟兩個類是共享的一個prototype,任何對成員的重定義都會互相影響,不是嚴格意義的繼承。但在這個思想的基礎上,可以利用反射機制來實作類的繼承,思路如下:利用for(…in…)語句枚舉出所有基類prototype的成員,并将其指派給子類的prototype對象。例如:

<script language="JavaScript" type="text/javascript">

<!--

function class1(){

      //構造函數

}

class1.prototype={

      method:function(){

           alert(1);

      },

      method2:function(){

           alert("method2");

      }

}

function class2(){

      //構造函數

}

//讓class2繼承于class1

for(var p in class1.prototype){

       class2.prototype[p]=class1.prototype[p];

}

//覆寫定義class1中的method方法

class2.prototype.method=function(){

      alert(2);

}

//建立兩個類的執行個體

var obj1=new class1();

var obj2=new class2();

//分别調用obj1和obj2的method方法

obj1.method();

obj2.method();

//分别調用obj1和obj2的method2方法

obj1.method2();

obj2.method2();

//-->

</script>

從運作結果可見,obj2中重複定義的method已經覆寫了繼承的method方法,同時method2方法未受影響。而且obj1中的method方法仍然保持了原有的定義。這樣,就實作了正确意義的類的繼承。為了友善開發,可以為每個類添加一個共有的方法,用以實作類的繼承:

//為類添加靜态方法inherit表示繼承于某類

Function.prototype.inherit=function(baseClass){

     for(var p in baseClass.prototype){

            this.prototype[p]=baseClass.prototype[p];

     }

}

這裡使用所有函數對象(類)的共同類Function來添加繼承方法,這樣所有的類都會有一個inherit方法,用以實作繼承。于是,上面代碼中的:

//讓class2繼承于class1

for(var p in class1.prototype){

       class2.prototype[p]=class1.prototype[p];

}

可以改寫為:

//讓class2繼承于class1

class2.inherit(class1)

這樣代碼邏輯變的更加清楚,也更容易了解。通過這種方法實作的繼承,有一個缺點,就是在class2中添加類成員定義時,不能給prototype直接指派,而隻能對其屬性進行指派,例如不能寫為:

class2.prototype={

      //成員定義

}

而隻能寫為:

class2.prototype.propertyName=someValue;

class2.prototype.methodName=function(){

      //語句

}

由此可見,這樣實作繼承仍然要以犧牲一定的代碼可讀性為代價,(注:prototype-1.3.1架構是一個JavaScript類庫,擴充了基本對象功能,并提供了實用工具。)中實作的類的繼承機制,不僅基類可以用對象直接指派給 property,而且在派生類中也可以同樣實作,使代碼邏輯更加清晰,也更能展現面向對象的語言特點。

<!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 class1() {
  }
        class1.prototype={
         p1:"p1",
            method1:function(){
                alert("from class1 method1");
            }
        }
function class2(){
}
        class2.prototype=class1.prototype;
        class2.prototype.p2="p2";
        class2.prototype.method1=function(){
          alert("from class2 method1");
      };

  class2.prototype.method2 =function(){
      alert("method2");
  };


  var cls1 = new class1();
  alert(cls1.p1);
  cls1.method1();

  var cls2 = new class2();
  alert(cls2.p1);
  alert(cls2.p2);
  cls2.method1();
  cls2.method2();


    </script>
</head>
<body>

<div id="mydiv" style="width:200px;height: 200px">

</div>

</body>
</html>
      

 用 inheritjs 代碼:

Function.prototype.inherit=function(baseClass){
    for(var p in baseClass.prototype){
        this.prototype[p]=baseClass.prototype[p];
    }
}
      

五:prototype-1.3.1架構自實作JS中的類的繼承

在prototype-1.3.1架構中,首先為每個對象都定義了一個extend方法:

//為Object類添加靜态方法:extend

Object.extend = function(destination, source) {

  for(property in source) {

     destination[property] = source[property];

  }

  return destination;

}

//通過Object類為每個對象添加方法extend

Object.prototype.extend = function(object) {

  return Object.extend.apply(this, [this, object]);

}

Object.extend 方法很容易了解,它是Object類的一個靜态方法,用于将參數中source的所有屬性都指派到destination對象中,并傳回 destination的引用。Object.prototype.extend的實作,因為Object是所有對象的基類,是以這裡是為所有的對象都添加一個extend方法,函數體中的語句如下:

Object.extend.apply(this,[this,object]);

這一句是将Object類的靜态方法作為對象的方法運作,第一個參數this是指向對象執行個體自身;第二個參數是一個數組,包括兩個元素:對象本身和傳進來的對象參數object。函數功能是将參數對象object的所有屬性和方法指派給調用該方法的對象自身,并傳回自身的引用。有了這個方法,下面看類繼承的實作:

<script language="JavaScript" type="text/javascript">

<!--

//定義extend方法

Object.extend = function(destination, source) {

  for (property in source) {

     destination[property] = source[property];

  }

  return destination;

}

Object.prototype.extend = function(object) {

  return Object.extend.apply(this, [this, object]);

}

//定義class1

function class1(){

      //構造函數

}

//定義類class1的成員

class1.prototype={

      method:function(){

           alert("class1");

      },

      method2:function(){

           alert("method2");

      }

}

//定義class2

function class2(){

      //構造函數

}

//讓class2繼承于class1并定義新成員

class2.prototype=(new class1()).extend({

      method:function(){

           alert("class2");

      }

});

//建立兩個執行個體

var obj1=new class1();

var obj2=new class2();

//試驗obj1和obj2的方法

obj1.method();

obj2.method();

obj1.method2();

obj2.method2();

//-->

</script>

從運作結果可以看出,繼承被正确的實作了,而且派生類的額外成員也可以以清單的形式加以定義,提高了代碼的可讀性。下面解釋繼承的實作:

//讓class2繼承于class1并定義新成員

class2.prototype=(new class1()).extend({

      method:function(){

           alert("class2");

      }

});

上段代碼也可以寫為:

//讓class2繼承于class1并定義新成員

class2.prototype=class1.prototype.extend({

      method:function(){

           alert("class2");

      }

});

但因為extend方法會改變調用該方法對象本身,是以上述調用會改變class1的prototype的值,在 prototype-1.3.1架構中,巧妙的利用new class1()來建立一個執行個體對象,并将執行個體對象的成員指派給class2的prototype。其本質相當于建立了class1的prototype 的一個拷貝,在這個拷貝上進行操作自然不會影響原有類中prototype的定義了。

整個代碼如下:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <title></title>
    <script type="text/javascript" src="js/Core.js"></script>
    <script type="text/javascript">
        function Class01(){

        }
        Class01.prototype={
            p1:"p1",
            method01:function(){
                alert("from class01 method01");
            }
        };

        function Class02(){

        }
        Class02.prototype =(new Class01()).extend({
            p2:"p2",
            method02:function(){
                alert("method02");
            },
            method03:function(){
                alert("method03");
            },
            method01:function(){
                alert("from class02 method01");
            }
        });

        var class01 = new Class01();
        alert(class01.p1);
        class01.method01();

        var class02 = new Class02();
        alert(class02.p1);
        alert(class02.p2);
        class02.method01();
        class02.method02();

    </script>
</head>
<body>

<div id="mydiv" style="width:200px;height: 200px">

</div>

</body>
</html>
      

 Core.js代碼:

Function.prototype.inherit=function(baseClass){
    for(var p in baseClass.prototype){
        this.prototype[p]=baseClass.prototype[p];
    }
}
Object.extend = function(destination, source) {
    for (property in source) {
        destination[property] = source[property];
    }
    return destination;
}
Object.prototype.extend = function(object) {
    return Object.extend.apply(this, [this, object]);
}
      

六:Prototype.js源碼解析

  參考:http://www.blogjava.net/TrampEagle/articles/30261.html

七:Prototype.js中類的繼承實作示例

參考:  http://www.108js.com/article/article6/60025.html?id=722

具體的代碼如下:

HTML頁:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <script type="text/javascript" src="js/prototype-1.6.0.3.js"></script>
    <script type="text/javascript" src="js/Person.js"></script>
    <script type="text/javascript" src="js/Employee.js"></script>
<script type="text/javascript">
    //建立一個類
   function getEmployeeInfo(){
       var employee = new Employee();
       employee.personName="wangwu";
       employee.personAge=28;
       employee.corpName="Google";

       var info = employee.showInfo();

       alert(info);
   }

</script>
</head>
<body>
<button onclick="getEmployeeInfo()">GetEmployeeInfo</button>
</body>
</html>
      

 本身Js代碼:

var Person = Class.create();
Person.prototype={
    //必須給初始化值
    initialize: function() {
    },
    personName:"zhang",
    personAge:18,
    showInfo:function(){
        alert(this.personName+","+this.personAge);
    }
}
      

 繼承的js 代碼:

var Employee = Class.create();

Employee.prototype = Object.extend(new Person(), {
    initialize: function() {
    },
    corpName:"Micosoft",
    showInfo:function(){
        return this.personName+","+this.personAge+","+this.corpName;
    }
});