2016年2月24日 星期三

JavaScript 原型式的物件導向繼承機制

在前一篇文章中我們提到唯有先搞清楚 JavaScript 中原型式的物件導向觀念與作法,才能在使用 JavaScript 這門語言時游刃有餘。但什麼是原型式的物件導向呢?
在大多數程式語言裡,每一個物件都是一個相關類別的實例(instance) ,這個類別提供了它所有實例所共用的程式碼。但在 JavaScript 裡是沒有類別這種東西的,取而代之是透過繼承其它物件來達到程式碼的共用。也就是說每個物件都會與其它的物件有關,這個關聯的物件就被稱為原型(雖然可以建立沒有原型的物件,但大部份而言,還是遵循上述規則)
以實字物件與內建建構式來說:
Var obj1 = new Object();
Var obj2 = {};

執行上述任一行程式,執行引擎會自動將 Object.prototype 設為新物件的原型
我們可以透過 ECMAScript  5 提供的 Object.getPrototypeOf 來查看這二個物件的原型:

console.log(Object.getPrototypeOf(obj1) === Object.prototype); //true
console.log(Object.getPrototypeOf(obj2) === Object.prototype); //true

也就是說不管使用實字物件方法或內建建構式中的任一種方法來建立新物件,新物件的原型一律是 Object.prototype,這會產生一種現象,當我們動態為任一物件的原型新增一個屬性或方法時,也會直接影影子到其它透過實字物件或內建建構式建立出來的物件,如下範例:

var obj1 = {};       //實字物件
           var obj2= new Object(); //內建建構式
           //動態透過 obj 物件在其原型上新增一個  say 方法
        obj1.constructor.prototype.say=function()  //constructor 後面在說明
          {
                alert('I am a prototype method');
          };
          obj1.say();    //output I am a prototype method
          obj2.say();    //I am a prototype method

 由以上的結果可以看出二個現象:
1.  在物件建立之後,再動態的在其原型物件上新增屬性或方法,仍然有效。
2. 原型上的屬性或方法是透過附加的方式,加到新物件上並非是採複製的方式,所以就算只修改任一物件的原型上的方法,也能立即反應在其它物件上,表示它們確實是共用一份程式碼,它們的關係如下:

  

  

,這個 Object prototype   JavaScript 所有物件之母

如果我們是透過自訂建構式函式建立新的物件又如何呢?

    function  Dog(name){
         this.name;
};

Dog.prototype.getName = function()[
     return this.name;
};

var dog = new Dog(‘harry’);
console.log(Object.getPrototypeOf(dog) ===Dog.prototype); //true
Dog 建構函式內建了一個預設的 prototype 屬性含有一個  
物件,它一 開始大致上是空的,它就是所謂的  dog 物件的   
原型,在上面的範例中,我們新增了一個方法到 
Dog.prototype 物件中:getName。當我們使用 new 建立了 Dog 
的一個實例時, dog 物件的原型就自動被設為儲存在 
Dog.prototype 中的物件,下圖顯示了它們的關係:
     
               

它一樣遵循著在物件建立之後,再動態的在其原型物件上新增屬性或方法,對已建立之物件仍然有效及原型上的屬性或方法是透過附加的方式,加到新物件上並非是採複製的方式,這二項規則,下列程式碼可以證明:


var Dog = function(name){
         this.name = "marry";
    };

    Dog.prototype.getName = function(){     
         return this.name;
    };
          
    var obj01 = new Dog("harry");
    var obj02 = new Dog("marry");
          
//在物件建立後才去修改原型裡的方法
    Dog.prototype.getName = function(){
   
     return  "harry" +"   "+"marry";
    }
console.log(obj01.getName()); // harry” +”   “+”marry”
console.log(obj02.getName());//“harry” +”   “+”marry”

二個新物件的 getName 方法都是輸出 “harry” +”   “+”marry”
這個特點和 Java C# 這種  class-base   OOPL 有很大的不同,也是 javaScript 最大的特點。因為在傳統的物件導向系統裡,我們是無法在物件產生之後再去修改類別並讓已建立的物件能立即發揮作用的。

也許我們又會追問,那函式預設的原型即然也是一個物件,按照定義,在 JavaScript 裡物件都有個原型,那這個函式的原型物件的原型又是什麼?透過下列實驗我們可以看出,Object.prototype 就是函式的原型物件的原型:

alert(Object.getPrototypeOf(Dog.prototype) === Object.prototype); //true

或者透過下列程式來驗證:
var Dog = function(name){
         this.name = name;
    };

    Dog.prototype.getName = function(){   
        return this.name;
    };
       
        var Cat = function(name){
         this.name = name;
    };
       
        Cat.prototype.getName = function(){    
         return this.name;
    };
       
       
    var obj01 = new Dog("I am dog");
    var obj02 = new Cat("I am cat");
       
        Object.getPrototypeOf(Dog.prototype).getdis = function(){
           alert('I am Object.prototype');
        }
         
       
        obj01.getdis(); //'I am Object.prototyp
    obj02.getdis();//'I am Object.prototyp

JavaScript 所有物件的原型基本上都可以追溯到這個物件,Object.prototype JavaScript 裡的萬物之母(基本上,在 JavaScript 裡除了這個物件沒有原型外,其它的物件都有原型)。這種一層一層往上委託的機制就是 JavaScript 裡所謂的物件的原型鏈,當我們需要存取任一物件的屬性或方法時,物件會先在本身定義的屬性裡尋找,如果沒有找到就往其原型裡找,如果還沒找到就再往其原型的原型裡找,直到找到最頂端,也就是  Object.prototype,如果都沒找到,就返回 undefined

至此我們可以稍微整理一下幾個重要的事實:
1. JavaScript 裡的所有函式都有一個 prototype 屬性,它存放一個稍為原型的物件。
2. JavaScript 的每個物件都擁有一個名為 constructor 的隱性屬性,指向當初用來建構該物件的建構式並且由於 prototype 原型屬性存在於建構式裡,所以我們可得到如下的關係圖:
    
    


  
  一個物件的原型除了可透過dog.constructor.prototype 取得外,也可利用ES5 提供 getPrototypeOf 方法取得,另外在某些瀏覽器裡,如 chrom   firefox ie9 以上,也為所有物件提供了一個 __proto__ 的屬性,當物件被建立時,這個 __proto__ 屬性的值就是它的構造函數的 prototype ,所以

   Object. getPrototypeOf(dog) === dog.constructor.prototype === dog.__proto__      === Dog.prototype
另外,一個物件如果己建立,緃使我們不知它原來的建構式為何?我們仍能透過 dog.constructor 來得知它原來的建構式是什麼,甚至透過它建立另一個實例,如下:
var dog2 = dog.constructor();


   prototype JavaScript 裡是如此的重要,以致於有那麼多種方式可以去存取到它, prototype 屬性類似於  class-base 等語言中的Class 做為物件的藍圖的角色,是實作 OOP 中繼承的機制 ,先了解這種基於原型的繼承機制我們才能繼續探索 JavaScript 中的封裝、多型及使用  JavaScript 特有原型物件導向機制來實作各種設計模式。

沒有留言:

張貼留言