ES5-對象建立與繼承----《JavaScript高程3》

建立對象的幾種方式

在邏輯上從低級到高級:工廠模式、構造函數模式、原型模式、組合模式。固然還有其餘模式,可是這四者邏輯關係強,總結起來頗有感受。之因此和繼承一塊兒分析,也是由於邏輯關係很清晰:原型模式對應原型鏈繼承,構造函數模式對應借用構造函數模式繼承,組合模式對應組合繼承。邏輯上按照「哪一個模式有什麼缺點,爲何有這個缺點,咱們怎麼解決這個缺點」逐步分析,這樣分析完後就會豁然開朗。javascript

1)工廠模式

使用函數建立對象,該函數來封裝建立對象的細節。java

function createObject(para1,para2,para3){
                  var o = new Object();//顯式建立對象
                  o.colors = [para1, para2, para3];
                  o.sayColors = function() {
                    console.log(this.colors[0]);
                  }
                  return o;//返回對象
                }
                var ob1 = createObject("red", "green", "blue");
                console.log(ob1);
                console.log(ob1.colors);
                ob1.sayColors();
                console.log(ob1 instanceof createObject);//false,沒法判斷對象的類型


缺點:沒法判斷對象類型。

2)構造函數模式

經過構造函數建立的實例被標識爲一種特定的類型,能夠經過instanceof 判斷對象類型。數組

function createObject(para1, para2, para3) {
              //經過this完成函數屬性定義,不用顯示建立對象,所以也不用返回對象
              this.colors = [
                para1,
                para2,
                para3
              ];
              this.sayColors = function () {
                console.log(this.colors[0]);
              }
            }
            var ob1 = new createObject('red', 'green', 'blue');
            console.log(ob1);
            console.log(ob1.colors);
            ob1.sayColors();
            console.log(ob1 instanceof createObject); //true,能夠判斷對象的類型

缺點:經過構造函數建立的實例具備不一樣的方法和屬性,屬性能夠不一樣,這對實例來講是好的,咱們但願實例屬性獨立。可是方法即函數,函數即對象,也就是說建立了太多重複的對象。函數

var ob1 = new createObject("red", "green", "blue");
  var ob2 = new createObject("red", "green", "blue");
  alert(ob1.sayColors == ob2.sayColors);//false,不一樣實例具備不一樣的方法

解決方式:把方法的定義放到構造函數外部,即在構造函數內部引用外部全局函數。這樣就能夠一次定義,屢次引用。可是當外部全局函數增多時,明顯下降了封裝性,《JavaScript精粹》上提到,全局對象是EScript的一大敗筆。this

<script type="text/javascript">
      function createObject(para1,para2,para3){
       //經過this完成函數屬性定義,不用顯示建立對象,所以也不用返回對象
        this.colors = [para1, para2, para3];
        this.sayColors=sayColors;
      }
      function sayColors(){
          alert(this.colors[0]);
        }
      var ob1 = new createObject("red", "green", "blue");
      var ob2 = new createObject("red", "green", "blue");
      alert(ob1.sayColors == ob2.sayColors);//true
      alert(ob1.colors == ob2.colors);//false ,在構造函數中建立的引用類型屬性是不一樣的
</script>

值得一提的是,構造函數建立的實例中的引用類型屬性是很特殊的,這一點會隨後提到。prototype

3) 原型模式

:每個函數都有一個prototype屬性,這個屬性指向經過構造函數建立的實例對象的原型對象。原型對象的方法和屬性能夠被它的全部實例共享。所以,經過把屬性和方法添加到實例的原型對象上,能夠實現屬性和方法共享。code

<script type="text/javascript">
      function createObject(){
  }
    createObject.prototype.colors = ["red", "green", "blue"];
    createObject.prototype.sayColors = function(){
    alert(this.colors[0]);
}

  var ob1 = new createObject();
  var ob2 = new createObject();
  alert(ob1.sayColors == ob2.sayColors);//true,經過原型建立的屬性和方法共享
  alert(ob1.colors == ob2.colors);//true
</script>

缺點:「成也蕭何,敗也蕭何」,原型模式的缺點就在於過強的共享能力,方法的共享能夠減小多餘的對象實例建立。可是屬性共享致使實例難以擁有本身獨特屬性。固然,若是是一些不會修改的屬性值,共享也就罷了;可是若是是須要修改的屬性,而且該屬性值是引用類型(基本類型屬性值能夠在實例中定義,會覆蓋掉原型屬性,可是不會修改原型屬性,其餘的實例訪問該屬性依舊對應原型屬性),那麼實例對這個屬性值的修改就會在原型中反映出來,這其實就是修改了原型。糟糕的是其餘實例中的該屬性也同步變化,而後就會出現奇怪的問題。對象

<script type="text/javascript">
  function createObject(){
  }
    createObject.prototype.colors = ["red", "green", "blue"];
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

  var ob1 = new createObject();
  var ob2 = new createObject();
  ob1.colors.push("yellow");
  alert(ob1.colors);//red,green,blue,yellow
  alert(ob2.colors);//red,green,blue,yellow,經過ob1作的改變在ob2反映出來
</script>

4) 組合模式(最經常使用的一種對象建立方式,兼顧優勢,避免缺點)

:使用構造函數模式定義各個實例屬性,使用原型模式定義方法和共享的屬性。繼承

<script type="text/javascript">
  function createObject(){
            this.colors = ["red", "green", "blue"];
  }
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

  var ob1 = new createObject();
  var ob2 = new createObject();
  alert(ob1.sayColors == ob2.sayColors);//true,經過原型建立的方法共享
  alert(ob1.colors == ob2.colors);//flase,構造函數建立的引用類型屬性不共享
</script>

補充關於對象的幾個方法:
isPrototypeOf(): 肯定一個對象是不是另外一個對象的原型,只要是原型鏈中出現的原型,就返回true,使用方法:ip

alert(createObject.prototype.isPrototypeOf(ob1));//rue

instanceof操做符:檢測是不是某一構造函數的實例,前面的參數是實例, 後面的的參數是構造函數名,只要是原型鏈中出現的構造函數就返回true。

alert(ob1 instanceof createObject);//true

hasOwnProperty(): 檢測一個實例是否擁有某個屬性,使用方法

alert(ob1.hasOwnProperty("colors"));//true

in操做符:單獨使用時能夠檢查實例屬性或者原型屬性是否存在,in後跟的通常是實例,所以能夠理解爲檢查實例以及實例對應的原型中是否有這個屬性或非法。使用方法:

alert("sayColors" in ob1);//true
  alert("sayColors" in createObject);//false,直接對原型檢查沒意義

for in 的另外一種用法,相似於數組的forEach()方法,是一個循環,返回實例或原型中的可枚舉的屬性

<script type="text/javascript">
  function createObject(){
            this.colors = ["red", "green", "blue"];
  }
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

   var ob1 = new createObject();
    for (var prop in ob1) {
         alert(prop); //前後彈出colors   sayColors
       }   
</script>

繼承的實現:

1.原型鏈

:把超類型的實例複製給子類型的原型,這樣超類型的方法和屬性就由子類型繼承。

<script type="text/javascript">
  //組合模式定義超類型對象
  function SuperType(){
    this.property = true;
  }
  SuperType.prototype.getSuperValue = function(){
    return this.property;
  };
  function SubType(){
    this.subproperty = false;
  }
  //經過建立超類型對象實例而且賦值給子類型原型實現繼承
  SubType.prototype = new SuperType();
  var instance = new SubType();
  //true,子類型訪問超類型的方法,實現了繼承
  alert(instance.getSuperValue()); 
</script>

問題:子類型原型會繼承超類型實例的屬性,若是這個屬性值是引用類型,就會致使子類型的全部實例都共享了這個屬性,致使不一樣實例屬性的耦合,這是原型模式建立對象的固有問題。而且,在建立子類型的實例時,沒法向超類型的構造函數傳遞參數。所以,實際中不多單獨使用原型鏈。

2 借用構造函數(經典繼承)

在子類型構造函數的內部調用超類型構造函數。這個方法之因此被稱爲借用構造函數,我以爲就是由於這種方法和前面介紹的經過構造函數建立實例的私有屬性是同樣的道理,只不過是在子類型構造函數內部調用了超類型構造函數。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];
  }
  //借用構造函數,在子類型構造函數的內部調用超類型構造函數
  function SubType(){
    SuperType.call(this);
  }
  //每次調用產生的實例具備不一樣的引用類型屬性
  var instance1 = new SubType();
  alert(instance1.colors);//red,blue,green
  instance1.colors.push("yellow");
  alert(instance1.colors);//red,blue,green,yellow

  var instance2 = new SubType();
  alert(instance2.colors);//red,blue,green,從原型繼承的實例屬性是私有的
  alert(ob1.colors == ob2.colors);//false,屬性私有
  alert(ob1.sayColors == ob2.sayColors);//false,方法也是私有
</script>

並且能夠在建立子類型實例時向超類型傳遞參數,由於這就至關於調用了一個函數,函數固然是能夠有參數的。
缺點是一樣具備構造函數模式建立對象的固有弊端:構造函數中煩人方法(函數對象)重複建立。而且,只有在超類型構造函數中定義的屬性和方法纔會被繼承,經過超類型原型定義的方法和屬性對子類型不可見,這是由於只執行了構造函數內部的語句。所以實際中這個方法也不多單獨使用。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];

  }
  //超類型的原型上建立方法
  SuperType.prototype.sayColors = function(){
      alert(this.colors[0]);
      return 0;
    }
  //借用構造函數,在子類型構造函數的內部調用超類型構造函數
  function SubType(){
    SuperType.call(this);
  }

  var ob1 = new SubType();
 var ob2 = new SuperType();

  alert(ob1.sayColors);//undefined,子類型不具備這個方法
  ob2.sayColors();//red,超類型具備這個方法
</script>

3 組合繼承(僞經典模式繼承)

將原型鏈繼承和借用構造函數模式組合到一塊兒,使用原型鏈實現對原型屬性和方法的繼承(實現了方法複用),可是經過借用構造函數實現對實例屬性的繼承(實現了實例屬性私有)。避免了缺陷,融合了優勢,是最經常使用的繼承模式,並且,instanceof操做符和isPrototypeOf()都適用於組合繼承建立的對象。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];

  }
  //超類型的原型上建立方法
  SuperType.prototype.sayColors = function(){
      alert(this.colors[0]);
      return 0;
    }
  //借用構造函數,在子類型構造函數的內部調用超類型構造函數,實現屬性繼承
  function SubType(){
    SuperType.call(this);
  }
  //原型鏈實現方法繼承
  SubType.prototype = new SuperType();

  var ob1 = new SubType();
  ob1.sayColors();//red//子類型繼承了超類型原型的方法
</script>
相關文章
相關標籤/搜索