標籤:原型鏈 pop 比較 特定 play str tor 預設 範圍
再上一篇的開頭說了建立對象的兩種方式,一種是Object建構函式的方式,一種是對象字面量的方法。但這些方式建立多個對象的時候都會產生大量的重複代碼。經過技術的進步也演化出來許多的建立對象的模式。本章會介紹 原廠模式,原型模式,建構函式模式和建構函式與原型模式的混合使用。
1,原廠模式
原廠模式是一個比較廣為人知的模式,這種模式將細節抽象出來。代碼如下
function createPerson(name,age,job){ var o =new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name) } return o;}var person1=createPerson("ds",12,"dada");var person2=createPerson("ds2",122,"dada2");
給他所需要的材料,他就會返回一個對象。解決了大量重複代碼的問題。可是又有一個問題,就是每個返回的對象都是Object。每個對象的的類型部分卻別開來。所以有個新的模式來解決這個問題。
2,建構函式模式
廢話不多說,還是先上代碼吧。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name) }}var person1=new Person("ds",12,"dada");var person2=new Person("ds2",122,"dada2");
第二種的使用了new的關鍵字。實際上經曆了4步。1,建立了一個新對象。2,將建構函式的範圍賦給建立的變數。3,執行代碼。4,返回新的對象。
person1和person2是不同的執行個體。兩個對像有一個Constructor(建構函式)指向Person的函數本身。而Constructor就是用來表示對象的類型。這也是建構函式模式和原廠模式的不同。而且想Object和Array的就是js中原生的建構函式。建立自訂的建構函式意味著可以為他的執行個體標識為一種特定的類型。
其實建構函式也是函數知識調用的方式不同而已。建構函式也可以普通的方式調用。
var p=new Object();Person.call(p,"sss",22,"222");
但是建構函式並不是沒有問題,比如說上面的那個SayName(),其實person1和person2的SayName其實並不是同一個Funcition的執行個體。也就是說每new一個對象,SayName本身的方法也被new了一次。所有執行個體的SayName都是獨立的並沒有指向同一個方法。解決的方法也有可以將方法提取到全域變數。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=sayName;}function sayName(){ alert(this.name)}
但是問題又來了,sayName作為全域變數的話卻只能被Person調用,有點不太符合全域變數的定義。重要的是如果方法很多的話又要在全域變數中添加許許多多的方法。好在這些問題都可以通過原型模式來解決
3,原型模式
在說原型模型的時候,應該先說明一下prototype(原型)是什嗎?其實它就是一個指標,指向一個對象。這個對象就是可以給特定類型的所有執行個體共用的屬性和方法。還是看代碼吧。
function Person(){}Person.prototype.name="shaoqi";Person.prototype.age=28;Person.prototype.job="IT";Person.prototype.sayName=function(){ alert(this.name);}var person1=new Pserson();person1.sayName();
解釋了對象,執行個體,原型和建構函式之間的關係。
在js中,只要建立一個新函數就會為該函數建立一個prototype的屬性。指向函數的原型對象。原型對象有都會有個constructor的指標,指向原函數。每個執行個體也有個指標[[prototype]],指向原型。其實每個執行個體和建構函式是沒有直接關係的,是通過原型將執行個體和建構函式關聯起來。
其實建構函式還有個更簡單的寫法。
function Person(){}Person.prototype={ name:"shaoqi", age:23, job:"It", SayName:function(){ alert(this.name); }}
但這樣又有個問題,這樣就相當與重寫了整個prototype,js本身是不會給它產生constructor的。也就是說這個原型對象中沒有指向Person的指標。但可以給它顯示的給它賦值。
function Person(){}Person.prototype={ constructor:Person, name:"shaoqi", age:23, job:"It", SayName:function(){ alert(this.name); }}
但還有個小問題,就是當這樣顯示的給原型指定建構函式的話,它的[[Enumerable]]會預設為true,原生的constructor是不能被枚舉的。如果一定要相容ECMAScript5的話可以改寫成下面代碼
function Person(){}Person.prototype={ name:"shaoqi", age:23, job:"It", SayName:function(){ alert(this.name); }}Object.defineProperty(Perosn.prototype,"constructor",{ enumerable:false, value:Person})
defineProperty這個方法的話在上一遍中有提到過。用來給屬性顯示的賦值特性的方法。
原型模式還有個動態性,應為本身在執行個體中它是已指標的形式存在的。可以動態給原型添加方法和屬性。可以動態添加到執行個體上面。當然原型對象也不是完美的,也是存在問題的。下面來看一段代碼。
function Person(){}Person.prototype={ constructor:Person, name:"shaoqi", age:23, job:"It", friends:["11","22","33"], SayName:function(){ alert(this.name); }}var person1=new Person();var person2=new Person();person1.friends.push("44");alert(person1.friends);alert(person2.friends);
問題就在於所有的執行個體(person1,person2)的指標都指向了一個數組。person1對數組操作之後,會改變原型中的資料,那麼person2的Friend也就變了。實際執行個體一般都是要有屬於自己的全部屬性的。所以這才是很少有人單獨使用原型模式的原因。
4,組合使用建構函式和原型模式
其實看到這裡,我們會發現原型模式和構造模式的一些問題。建構函式模式是所有的屬性都是為每一個執行個體重新的初始化一個出來。而原型模式則是為所有的執行個體公用一個屬性或者方法。其實兩種方法都有點極端,在一個對象中,其實情況是多樣的,有些屬性需要獨立,有些需要共用。所有就有了這種組合模式的出現。可謂是取了兩種模式之長。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["11","22"]}Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); }}
將需要獨立出來屬性放在建構函式裡面進行初始化,然後類似方法的屬性則通過原型的方式進行賦值。
這是在ECMAScript5中使用最廣泛的建立自訂類型方式。
5,動態原型模式
其實在對象語言中如C#中是沒有原型一說的,只有一個建構函式。為了盡量的消除這點差異便又來動態原型模式。它就是將所有的資訊都封裝到了建構函式中。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["11","22"]; if(typeof this.sayName != "function") { Person.prototype.sayName=function(){ alert(this.name) } }}
不同之處就是在建構函式中有個判斷。這個判斷保證裡面的代碼只在初始化的時候運行一遍。這樣就可用同一個建構函式來完成組合模式的作用了。
6,寄生模式
這是一個比較少用的模式。其實他和工廠方式沒什麼太大的區別。看個例子。
function Person(name,age,job){ var o =new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name) } return o;}var person1=new Person("dd",12,"dd");
是不是和原廠模式一毛一樣??其實唯一的區別就在於調用的時候,寄生模式使用了new的方法進行初始化。這種模式一般使用在什麼情境呢?
假設我們想在原生的Array的基礎上改造一下便可以這樣
function SpecialArray(){ var value=new Array(); value.push.apply(values,arguments); value.toPopedString=function(){ return this.join("|"); } return value;}var colors=new SpecialArray("11","2","33");alert(colors.toPopedString());
在原有的類型上進行改造,可以用到寄生模式,有點類似於c#中的擴充。但是這樣產生的執行個體和建構函式,原型是沒有任何關係的。所以建議在可以使用它模式的前提下就不要用這種模式了。
這一篇吧對象的建構函式說完了,也說了原型的原理。下一篇就開始繼承和原型鏈了。
js物件導向程式設計之建構函式