Javascript之 對象和原型

      雖然Object構造函數或對象字面量均可以用來建立單個對象,但這種方式有個弊端:使用同一個接口建立不少對象,會產生大量的重複代碼。爲了解決這個問題,因而百家爭鳴,各類工廠模式的變體應運而生。程序員

     1.工廠模式設計模式

        這種模式是軟件工廠領域的廣爲人知的設計模式,它抽象了建立具體對象的過程,用函數來封裝以特定接口建立對象的細節,舉個栗子:數組

 1 function createPerson(name,age,job){
 2             var o=new Object(); 3 o.name=name; 4 o.age=age; 5 o.job=job; 6 o.sayname = function(){ 7 alert(this.name); 8  }; 9 return o; 10  } 11 12 var person1=createPerson("Sleipnir",23,"Software"); 13 var person2=createPerson("xiaoxiao",24,"Student");

       這個模式雖然能夠無數次調用,解決了建立多個類似對象的問題,但沒有解決對象識別的問題(即怎樣知道一個對象的類型)函數

      2.構造函數模式this

        咱們先用構造函數模式把工廠模式的例子重寫一遍:spa

 1  function Person(name,age,job){
 2                 this.name=name; 3 this.age=age; 4 this.job=job; 5 this.sayname = function(){ 6 alert(this.name); 7  }; 8  } 9 10 var person1=new Person("Sleipnir",23,"Software"); 11 var person2=new Person("xiaoxiao",24,"Student");

        跟以前的工廠模式,咱們能夠看出區別:1.沒有在函數內再建立對象prototype

                                                                        2.直接將屬性和方法賦給了this對象設計

                                                                        3.沒有return語句指針

        在建立Person的實例時,必須用到new操做符。以這種方式調用構造函數會經歷如下4個步驟:1.建立一個新對象  2.將構造函數的做用域賦值給新對象(所以this就指向了這個新對象) 3.執行構造函數中的代碼(爲新對象添加屬性和方法)   4.返回新對象code

        剛纔說了,構造函數勝於工廠模式的大方在於,它能解決實例的對象類型問題,未來能夠將它的實例標識爲一種特定類型:

1 console.log(person1 instanceof Person);  //true
2 console.log(person1 instanceof Object]);  //true
3 console.log(person2 instanceof Person);  //true
4 console.log(person2 instanceof Object);  //true
5 console.log(Person instanceof Object);  //true

        person1和person2之因此同時是Object的實例,是由於全部對象均繼承自Object(下面會講到繼承)

      2.1 調用構造函數的方式

         上面已經寫了Person的構造函數,咱們來用幾種不一樣的方式調用:

 1 //當作構造函數使用
 2 var person= new Person("Sleipnir",23,"Software");
 3 person.sayname();  //"Sleipnir"
 4 
 5 //做爲普通函數使用
 6 Person("xiaoxiao",25,"Student");  //添加到全局window
 7 window.sayname();  //"xiaoxiao"
 8 
 9 //在另外一個對象的做用域中調用
10 var o=new Object(); 11 person.call(o,"xiaoxiao",25,"Student"); 12 o.sayname(); //"xiaoxiao"

           第一種是當作構造函數使用,以前已經提過,;第二種是做爲普通函數調用,由於this對象都是指向全局對象,因此屬性和方法都被添加了window對象;第三種是在o對象的做用域中使用call()方法來調用Perso函數

        2.2 構造函數的問題

             構造函數雖然不錯,但也有瑕疵。主要問題就是每一個方法須要在每一個實例上從新建立一遍,可是,若是在構造函數裏面建立Function實例或者在構造函數外部定義函數來供構造函數建立的實例調用的話,那咱們所謂的構造函數的全局做用域就名存實亡,自定義的引用類型包括定義的方法也就沒有封裝性可言了。

             好在程序員們都是不服輸的人,歷史的車輪老是向前滾動,因而原型模式就登上歷史舞臺了。

        3.原型模式

            概念先不說,舉個栗子再解釋:

 1 function Person(){
 2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8  }; 9 10 var person1=new Person(); 11 person1.sayname(); //"Sleipnir" 12 var person2=new Person(); 13 person2.sayname(); //"Sleipnir" 14 15 alert(person1.sayname==person2.sayname);

 

          咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性是個指針,指向一個對象。因此,prototype就是經過調用構造函數而建立的那個對象實例的原型對象。上面例子中的person1和person2所擁有的屬性和方法都是來源於構造函數的原型對象中的

          想要理解原型模式,就要理解【構造函數】【原型】【實例】這三種之間的關係,抽象出來簡單來講,原型是構造函數的屬性,而實例是經過構造函數的原型而建立的,實例和構造函數沒有關係,原型裏還有一個constructor是指向構造函數的,constructor就是相似於指針的存在,構造函數經過constructor的指針做用,就把原型和實例鏈接起來了。這也是最簡單的原型繼承關係。

      3.1 訪問實例/原型的屬性

         有時候,咱們根據原型建立的實例,這個實例裏有一部分是原型的屬性,有一部分也是實例本身的屬性,因而就要判斷哪些屬性屬於原型,哪些屬性屬於實例本身

         有兩個方法:hasOwnProperty() 和 in操做符

         先看hasOwnProperty():

 1 function Person(){
 2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8  }; 9 10 var person1=new Person(); 11 var person2=new Person(); 12 13 console.log(person1.hasOwnProperty("name")); //false 14 15 person1.name="xiaoxiao"; 16 console.log(person1.name); //"xiaoxiao" 17 console.log(person1.hasOwnProperty("name")); //true 18 19 console.log(person2.name); //"Sleipnir" 20 console.log(person2.hasOwnProperty("name")); //false 21 22 delete person1.name; 23 console.log(person1.name); //"Sleipnir" 24 console.log(person1.hasOwnProperty("name")); //false

       從代碼結果能夠總結出來,hasOwnProperty()檢測的是實例屬性,若是是屬於實例的,返回true,不然返回false

       in操做符:

function Person(){
}
Person.prototype.name="Sleipnir"; Person.prototype.age=23; Person.prototype.job="Software"; Person.prototype.sayname=function(){ alert(this.name); }; var person1=new Person(); var person2=new Person(); console.log(person1.hasOwnProperty("name")); //false console.log("name" in person1); //true  person1.name="xiaoxiao"; console.log(person1.name); //"xiaoxiao" console.log(person1.hasOwnProperty("name")); //true console.log("name" in person1); //true  console.log(person2.name); //"Sleipnir" console.log(person2.hasOwnProperty("name")); //false console.log("name" in person2); //true delete person1.name; console.log(person1.name); //"Sleipnir" console.log(person1.hasOwnProperty("name")); //false console.log("name" in person1); //true

        經過互相比較,咱們能看出來,直接使用in操做符是,不管屬性是實例本身的仍是原型的,都會返回true

        咱們來定義一個函數,組合使用hasOwnProperty()和in操做符:

                                function hasPrototypeProperty(object,name){
                                                     return !object.hasOwnProperty(name)&&(name in object); 
                                                                }

 1 function Person(){
 2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8 }; 9 function hasPrototypeProperty(object,name){ 10 return !object.hasOwnProperty(name)&&(name in object); 11  } 12 var person=new Person(); 13 console.log(hasPrototypeProperty(person,"name")); //true 14 15 person.name="xiaoxiao"; 16 console.log(hasPrototypeProperty(person,"name")); //false

      3.2  更簡單的原型語法

         使用Person.prototype一個個定義屬性太繁瑣,因而咱們能夠用對象字面量給原型定義屬性:

 1 function Person(){
 2 } 3 Person.prototype={ 4  constructor:Person, 5 name:"Sleipnir", 6 age:23, 7 job:"Software", 8 sayname: function(){ 9 alert(this.name); 10  } 11 };

         這裏要注意,constructor要設置爲Person,否則就會切斷原型與實例之間的關係了

     3.3 原型模式的問題

         原型模式省略了爲構造函數傳遞初始化參數這一環節,結果全部實例在默認狀況下都取得了相同的屬性值。

         原型模式的問題在於其共享的本質,咱們來看一個因爲這個緣由而引發問題的栗子:

function Person(){
  }
  Person.prototype={ constructor:Person, name:"Sleipnir", age:23, job:"Software", friends:["zhangsan","lisi"], sayname: function(){ alert(this.name); } }; var person1=new Person(); var person2=new Person(); person1.friends.push("van"); console.log(person1.friends); //"zhangsan,lisi,van" console.log(person2.friends); //"zhangsan,lisi,van" console.log(person1.friends===person2.friends); //true

   這個例子中,friends是個引用類型的數組,因爲在原型中已經定義了,因此在實例中作修改時,修改的值也會映射到其原型,而後原型又會同時將數據更新加載到其餘實例中,這也體現了原型的動態性。但這種效果並非咱們想要的。

  實際需求是,原型幫咱們定義一部分公共屬性和方法,而後實例本身也有獨立的屬性和方法,基於這種狀況,便有了下面的模式。

    4.組合使用構造函數模式和原型模式

       在這種模式下,構造函數用於定義實例屬性,原型模式用於定義方法和共享的屬性。結果,每一個實例都有了本身的一份實例屬性的副本,同時也共享着對方法的引用,最大限度地節省內存,同時這種模式還支持向構造函數傳遞參數,可謂集兩種模式之長,咱們來看看用這個方式重寫的上面的例子:

 1 function Person(name,age,job){
 2        this.name=name; 3 this.age=age; 4 this.job=job; 5 this.friends=["zhangsan","lisi"]; 6 } 7 Person.prototype={ 8  constructor:Person, 9 sayname: function(){ 10 alert(this.name); 11  } 12 } 13 14 var person1=new Person("Sleipnir",23,"Software"); 15 var person2=new Person("xiaoxiao",25,"Student"); 16 17 person1.friends.push("van"); 18 console.log(person1.friends); //"zhangsan,lisi,van" 19 console.log(person2.friends); //"zhangsan,lisi" 20 console.log(person1.fgriends===person2.friends); //false 21 console.log(person1.sayname===person2.sayname); //true

        這種構造模式和原型混合的模式,是目前使用最多的一種方法,能夠說,這是用來定義引用類型的一種默認模式。

 

   5.動態原型模式

       這種模式是解決了在構造器中查看並初始化原型的問題,它的動態性在於,在構造器中檢查一下原型中是否存在某個方法,若是不存在,就創建下,創建一次就好了,一勞永逸,這就是其方便之處,咱們一樣來看個例子:

 1 function Person(name,age,job){
 2          this.name=name; 3 this.age=age; 4 this.job=job; 5 6 //檢查原型中是否有方法 7 if(typeof this.sayname!="function"){ 8 Person.prototype.sayname=function(){ 9 alert(this.name); 10  }; 11  } 12 }
相關文章
相關標籤/搜索