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.構造函數模式
? ? ? ? 我們先用構造函數模式把工廠模式的例子重寫一遍:
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.沒有在函數內再創建對象
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.直接將屬性和方法賦給了this對象
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 3.沒有return語句
? ? ? ? 在創建Person的實例時,必須用到new操作符。以這種方式調用構造函數會經歷以下4個步驟:1.創建一個新對象??2.將構造函數的作用域賦值給新對象(因此this就指向了這個新對象)?3.執行構造函數中的代碼(為新對象添加屬性和方法)?? 4.返回新對象
? ? ? ? 剛才說了,構造函數勝于工廠模式的大方在于,它能解決實例的對象類型問題,將來可以將它的實例標識為一種特定類型:
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"); //添加到全局window7 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);?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? 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 }?
? ??
轉載于:https://www.cnblogs.com/sleipnir-0430/p/8562948.html
總結
以上是生活随笔為你收集整理的Javascript之 对象和原型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux下创建用户及组
- 下一篇: cocurrent包semaphore信