早期的博客3
創(chuàng)建對(duì)象的方法:
- object構(gòu)造函數(shù)和對(duì)象字面量方法
- 工廠模式
- 自定義構(gòu)造函數(shù)模式
- 原型模式
- 組合使用自定義構(gòu)造函數(shù)和原型模式
- 動(dòng)態(tài)原型模式、寄生構(gòu)造模式、穩(wěn)妥構(gòu)造函數(shù)模式
一、Obejct構(gòu)造函數(shù)和對(duì)象字面量方法
它們的優(yōu)點(diǎn)是創(chuàng)建單個(gè)的對(duì)象非常方便。但是有一個(gè)明顯的缺點(diǎn):利用同一接口函數(shù)創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。
//Object構(gòu)造函數(shù)
var person = new Object();
person.name = "huazhen";
person.age = 22;
person.job = "yaoniguan";
person.sayName = function(){
alert(this.name);
};
//對(duì)象字面量方法
var person = {
name:"huazhen",
age:22,
job:"yaoniguan",
sayName:function(){
alert(this.name);
}
};
如何理解:利用同一接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。示例如下:
//創(chuàng)建person1對(duì)象
var person1 = {
name:"huazhen",
age:22,
job:"yaoniguan",
sayName:function(){
alert(this.name);
}
};
//創(chuàng)建person2對(duì)象
var person2 = {
name:"heiheihei",
age:25,
job:"hehe",
sayName:function(){
alert(this.name);
}
};
可以看出,當(dāng)我們創(chuàng)建兩個(gè)類似的對(duì)象時(shí),重復(fù)寫了name,age,job以及對(duì)象的方法這些代碼,隨著類似對(duì)象的增多,顯然,代碼會(huì)凸顯出復(fù)雜、重復(fù)的感覺(jué)。為解決這個(gè)問(wèn)題,就要了解工廠模式。
二、工廠模式
為解決創(chuàng)建多個(gè)對(duì)象產(chǎn)生大量重復(fù)代碼的問(wèn)題,由此產(chǎn)生了工廠模式。那么,究竟什么是工廠模式?這種模式抽象了創(chuàng)建具體對(duì)象的過(guò)程。考慮到在ECMAScript中無(wú)法創(chuàng)建類,開(kāi)發(fā)人員就發(fā)明了一種函數(shù),用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié),如下面例子:
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("huazhen",22,"yaoniguan");
var person2 = createPerson("heiheihei",25,"hehe");
可以無(wú)數(shù)次的調(diào)用這個(gè)函數(shù),每次它都會(huì)返回包含三個(gè)屬性一個(gè)方法的對(duì)象。成功解決了Object構(gòu)造函數(shù)或?qū)ο笞置媪縿?chuàng)建單個(gè)對(duì)象而造成大量代碼重復(fù)的問(wèn)題。工廠模式有以下特點(diǎn):
- 在函數(shù)內(nèi)部顯式的創(chuàng)建了對(duì)象。
- 在函數(shù)結(jié)尾處一定要返回這個(gè)新創(chuàng)建的對(duì)象。
但是可以發(fā)現(xiàn),工廠模式創(chuàng)建的對(duì)象,例如這里的person1和person2,我們無(wú)法識(shí)別對(duì)象是什么類型(在示例中國(guó)得到的都是o對(duì)象,對(duì)象的類型都是Object),為了解決這個(gè)問(wèn)題,自定義構(gòu)造函數(shù)模式出現(xiàn)了。
三、構(gòu)造函數(shù)模式
既然自定義構(gòu)造函數(shù)模式是為了解決無(wú)法直接識(shí)別對(duì)象的類型才出現(xiàn)的,那么顯然自定義構(gòu)造函數(shù)需要解決兩個(gè)問(wèn)題。其一:直接識(shí)別創(chuàng)建的對(duì)象的類型。其二:解決工廠模式所解決的創(chuàng)建大量類似對(duì)象產(chǎn)生的代碼重復(fù)問(wèn)題。
在第一部分中,我們使用Object構(gòu)造函數(shù)是原生構(gòu)造函數(shù),顯然不能解決問(wèn)題。我們可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對(duì)象類型的屬性和方法。使用構(gòu)造函數(shù)模式將前面的例子重寫,代碼如下:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("huazhen",22,"yaoniguan");
var person2 = new Person("heiheihei",25,"hehe");
我們驗(yàn)證第一個(gè)問(wèn)題,是否可以識(shí)別創(chuàng)建的對(duì)象的類型:
//檢測(cè)對(duì)象類型:
alert(person1 instanceof Object); //true
alert(person2 instanceof Person); //true
alert(person1 instanceof Object); //true
alert(person2 instanceof Person); //true
person1和person2之所以同時(shí)是Object的實(shí)例,是因?yàn)樗袑?duì)象均繼承自O(shè)bject。
對(duì)于第二個(gè)問(wèn)題,顯然創(chuàng)建大量的對(duì)象不會(huì)造成代碼的重復(fù)。
1.對(duì)比自定義構(gòu)造函數(shù)與工廠模式的不同之處:
自定義構(gòu)造函數(shù)沒(méi)有用var o = new Oeject() 那樣顯示的創(chuàng)建對(duì)象;
與o.name不同,它直接將屬性和方法賦給this對(duì)象,this最終會(huì)指向新創(chuàng)建的對(duì)象
因?yàn)闆](méi)有創(chuàng)建對(duì)象,所以最終沒(méi)有return一個(gè)對(duì)象。
2.對(duì)于構(gòu)造函數(shù)需要注意:
構(gòu)造函數(shù)的函數(shù)名需要大寫,用以區(qū)分普通函數(shù)。
構(gòu)造函數(shù)也是函數(shù),只是它的作用之一是創(chuàng)建對(duì)象。
構(gòu)造函數(shù)在創(chuàng)建新對(duì)象時(shí),需要使用new操作符。
創(chuàng)建的兩個(gè)對(duì)象person1和person2的constructor(構(gòu)造函數(shù))屬性都指向用于創(chuàng)建它們的Person構(gòu)造函數(shù)。
3.理解構(gòu)造函數(shù)也是函數(shù):
構(gòu)造函數(shù)與其他函數(shù)的唯一區(qū)別,就在于調(diào)用它們的方式不同,不過(guò),構(gòu)造函數(shù)畢竟也是函數(shù),不存在定義構(gòu)造函數(shù)的特殊語(yǔ)法。任何函數(shù),只要通過(guò)new操作符來(lái)調(diào)用,那它就可以作為構(gòu)造函數(shù)。如下代碼所示:
//當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("huazhen",22,"yaoniguan");
person.sayName(); //"huazhen"
//當(dāng)作普通函數(shù)調(diào)用
Person("heiheihei",25,"hehe"); //添加到window
window.sayName(); //"heiheihei"
當(dāng)在全局作用域中調(diào)用一個(gè)函數(shù)時(shí),this對(duì)象總是指向Global對(duì)象(在瀏覽器中就是window對(duì)象)。
4.構(gòu)造函數(shù)的問(wèn)題:
使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)示例上重新創(chuàng)建一遍。在前面的例子中,person1和person2都有一個(gè)名為sayName()的方法,但是兩個(gè)方法不是同一個(gè)Function的實(shí)例。函數(shù)也是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。從邏輯角度,構(gòu)造函數(shù)也可以這樣定義:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("alert(this.name)"); //與聲明函數(shù)在邏輯上是等價(jià)的
}
以下代碼可以證明不同實(shí)例上的同名函數(shù)是不相等的:alert(person1.sayName == person2.sayName) //false
5.解決方法:
創(chuàng)建兩個(gè)完成相同任務(wù)的Function實(shí)例的確沒(méi)有必要,況且有this對(duì)象在,根本不用執(zhí)行代碼前就把函數(shù)綁定到特定對(duì)象上面。因此,通過(guò)把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來(lái)解決這個(gè)問(wèn)題。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person("huazhen",22,"yaoniguan");
var person2 = new Person("heiheihei",25,"hehe");
把sayName()函數(shù)的定義轉(zhuǎn)移到構(gòu)造函數(shù)外部,而在構(gòu)造函數(shù)內(nèi)部,我們將sayName屬性設(shè)置成全局的sayName函數(shù)。由于sayName包含的是一個(gè)指向函數(shù)的指針,因此person1和person2對(duì)象就共享了全局作用域中定義的同一個(gè)sayName()函數(shù),這樣就解決了兩個(gè)函數(shù)做同一件事情的問(wèn)題。這樣又有新的問(wèn)題;
6.新的問(wèn)題:
- 在全局作用域中定義的函數(shù)實(shí)際上只能被某個(gè)對(duì)象調(diào)用,這讓全局作用域有點(diǎn)名不副實(shí)。
- 如果對(duì)象需要定義很多方法,那么就要定義很多個(gè)全局函數(shù),那么就沒(méi)有絲毫的封裝性可言。
接下來(lái),用原型模式來(lái)解決這個(gè)問(wèn)題。
四、原型模式
為什么會(huì)出現(xiàn)原型模式呢?這個(gè)模式在上面講了是為了解決自定義構(gòu)造函數(shù)需要將方法放在構(gòu)造函數(shù)之外造成封裝性較差的問(wèn)題。當(dāng)然它又要解決構(gòu)造函數(shù)能夠解決的問(wèn)題,所以,最終它需要解決以下幾個(gè)問(wèn)題。其一:可以直接識(shí)別創(chuàng)建的對(duì)象的類型。其二:解決工廠模式解決的創(chuàng)建大量相似對(duì)象時(shí)產(chǎn)生的代碼重復(fù)的問(wèn)題。其三:解決構(gòu)造函數(shù)產(chǎn)生的封裝性不好的問(wèn)題。
1.理解原型對(duì)象
首先,我們應(yīng)當(dāng)知道:無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù)(函數(shù)即對(duì)象),就會(huì)根據(jù)一組特定的規(guī)則創(chuàng)建一個(gè)函數(shù)(對(duì)象)的prototype屬性(理解為指針),這個(gè)屬性會(huì)指向函數(shù)的原型對(duì)象(原型對(duì)象也是一個(gè)對(duì)象),但是因?yàn)槲覀儾荒芡ㄟ^(guò)這個(gè)新函數(shù)訪問(wèn)prototype屬性,所以寫為[[prototype]]。同時(shí),對(duì)于創(chuàng)建這個(gè)對(duì)象的構(gòu)造函數(shù)也將獲得一個(gè)prototype屬性(理解為指針),同時(shí)指向它所創(chuàng)建的函數(shù)(對(duì)象)所指向的原型對(duì)象,這個(gè)構(gòu)造函數(shù)是可以直接訪問(wèn)prototype屬性的,所以我們可以通過(guò)訪問(wèn)它將定義對(duì)象實(shí)例的信息直接添加到原型對(duì)象中。這時(shí)原型對(duì)象擁有一個(gè)constructor屬性(理解為指針)指向創(chuàng)建這個(gè)對(duì)象的構(gòu)造函數(shù)(注意:這個(gè)constructor指針不會(huì)指向除了構(gòu)造函數(shù)之外的函數(shù))。
示例:
function Person(){}
Person.prototype.name = "huazhen";
Person.prototype.age = 22;
Person.prototype.job = "yaoniguan";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
person1.sayName(); //huazhen
person2.sayName(); //huazhen
alert(person1.sayName == person2.sayName) //true
這個(gè)例子中,首先創(chuàng)建了一個(gè)內(nèi)容為空的構(gòu)造函數(shù),因?yàn)榭梢酝ㄟ^(guò)訪問(wèn)構(gòu)造函數(shù)的prototype屬性來(lái)為原型對(duì)象中添加屬性和方法。于是在下面幾行代碼中,我們便通過(guò)訪問(wèn)構(gòu)造函數(shù)的prototype屬性向原型對(duì)象中添加了屬性和方法。接著,創(chuàng)建了兩個(gè)對(duì)象實(shí)例person1和person2,并調(diào)用了原型對(duì)象中sayName()方法,得到了原型對(duì)象中的name值。這說(shuō)明:構(gòu)造函數(shù)創(chuàng)建的每一個(gè)對(duì)象和實(shí)例都擁有或者說(shuō)是繼承了原型對(duì)象的屬性和方法。(因?yàn)闊o(wú)論是創(chuàng)建的對(duì)象實(shí)例還是創(chuàng)造函數(shù)的prototype屬性都是指向原型對(duì)象的) 換句話說(shuō),原型對(duì)象中的屬性和方法會(huì)被構(gòu)造函數(shù)所創(chuàng)建的對(duì)象實(shí)例所共享,這也是原型對(duì)象的一個(gè)好處。
闡釋這個(gè)問(wèn)題:
從這個(gè)圖中,我們可以看出:
- 構(gòu)造函數(shù)和由構(gòu)造函數(shù)創(chuàng)建的對(duì)象的prototype指針都指向原型對(duì)象。即原型對(duì)象即是構(gòu)造函數(shù)的原型對(duì)象,又是構(gòu)造函數(shù)創(chuàng)建的對(duì)象的原型對(duì)象。
- 原型對(duì)象有一個(gè)constructor指針指向構(gòu)造函數(shù),卻不會(huì)指向構(gòu)造函數(shù)創(chuàng)建的實(shí)例。
- 構(gòu)造函數(shù)的實(shí)例[[prototype]]屬性被實(shí)例訪問(wèn)來(lái)添加或修改(屏蔽)原型對(duì)象的屬性和方法的,而構(gòu)造函數(shù)的prototype屬性可以被用來(lái)訪問(wèn)以修改原型對(duì)象的屬性和方法。
- person1和person2與它們的構(gòu)造函數(shù)之間沒(méi)有直接的關(guān)系,只是它們的prototype屬性同時(shí)指向了同一個(gè)原型對(duì)象而已。
- Person.prototype指向了原型對(duì)象,而Person.prototype.constructor又指回了Person。
- 雖然這兩個(gè)實(shí)例都不包含屬性和方法,但卻可以調(diào)用person1.name,這是通過(guò)查找對(duì)象屬性的過(guò)程來(lái)實(shí)現(xiàn)的。
2.有關(guān)于原型對(duì)象中的方法以及實(shí)例中的屬性和原型對(duì)象中的屬性
兩種方法來(lái)確定構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象與原型對(duì)象之間的關(guān)系:
第一種方法:isPortotypeOf()方法,通過(guò)原型對(duì)象調(diào)用,確定原型對(duì)象是否是某個(gè)實(shí)例的原型對(duì)象。比如
alert(Person.prototype.idPortotyprOf(person1)); //true
alert(Person.prototype.idPortotyprOf(person2)); //true
即說(shuō)明person1實(shí)例和person2實(shí)例的原型對(duì)象都是Person.prototype
第二種方法:Object.getPrototypeOf()方法,通過(guò)此方法得到某個(gè)對(duì)象實(shí)例的原型,即[[Prototype]]。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(Person1).name); //huazhen
問(wèn)題:當(dāng)實(shí)例本身自己有和原型中相同的屬性名,而屬性值不同,在代碼獲取某個(gè)對(duì)象的屬性時(shí),該從哪里獲取呢?
規(guī)則:在代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。如果實(shí)例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性。如果在原型對(duì)象中找到了這個(gè)屬性,則會(huì)返回該屬性的值。
例:
function Person(){}
Person.prototype.name = "huazhen";
Person.prototype.age = 22;
Person.prototype.job = "yaoniguan";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "heiheihei";
alert(person1.name); //heiheihei--來(lái)自實(shí)例
alert(person2.name); //huazhen--來(lái)自原型
delete person1.name;
alert(person1.name); //huazhen--來(lái)自原型
- 首先,把person1實(shí)例的name屬性設(shè)置為"heiheihei",當(dāng)我們直接獲取person1的name屬性時(shí),會(huì)出現(xiàn)在person1本身找該屬性,找不到則在原型對(duì)象中尋找。
- 當(dāng)給person1對(duì)象添加了自身的屬性name時(shí),會(huì)得到person1自身的屬性,也就輸該屬性屏蔽了原型中的同名屬性
- 最后,可以通過(guò)delete刪除實(shí)例中的屬性,而原型中的屬性不會(huì)被刪除。
第三種方法:hasOwnProperty()方法,該方法可以檢測(cè)一個(gè)屬性是存在于實(shí)例還是存在于原型中。只有給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回true,否則返回false。
例:
function Person(){}
Person.prototype.name = "huazhen";
Person.prototype.age = 22;
Person.prototype.job = "yaoniguan";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
//下面使用該方法:
alert(person1.hasOwnProperty("name")); //false
person1.name = "heiheihei";
alert(person1.name); //heiheihei--來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //huazhen--來(lái)自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //huazhen--來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
3.in操作符的使用
in操作符會(huì)在通過(guò)對(duì)象能夠訪問(wèn)給定屬性時(shí),返回true,無(wú)論該屬性存在于事例還是原型中。
例:
function Person(){}
Person.prototype.name = "huazhen";
Person.prototype.age = 22;
Person.prototype.job = "yaoniguan";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
//下面使用方法
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "heiheihei";
alert(person1.name); //heiheihei--來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //huazhen--來(lái)自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
alert(hasPrototypeProperty(person2,name)); //true
delete person1.name;
alert(person1.name); //huazhen--來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
可以看到,無(wú)論屬性存在于實(shí)例對(duì)象本身還是在實(shí)例對(duì)象的原型對(duì)象都會(huì)返回true。用in操作符以及hasOwnProperty()方法就可以判斷一個(gè)屬性是否存在于原型對(duì)象(而不是存在于對(duì)象實(shí)例或者是不存在)。
hasPrototypeProperty()函數(shù):
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in Object);
在函數(shù)中in操作符返回true而hasOwnProperty()方法返回false,那么如果得到true則說(shuō)明對(duì)象一定存在于原型對(duì)象中。(注意!的優(yōu)先級(jí)高于&&)
4.for-in循環(huán)和Object.keys()方法在原型中的使用
在通過(guò)for-in循環(huán)時(shí),它返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的屬性,其中既包含存在于實(shí)例中的屬性,也包括存在于原型中的屬性。且對(duì)于屏蔽了原型中不可枚舉的屬性(即將[[Enumberable]]標(biāo)記為false的屬性)也會(huì)在for-in中循環(huán)返回。
for(var prop in person1){
alert(prop); //name,age,job,sayName
}
通過(guò)for-in循環(huán),可以枚舉出name,age,job,sayName這幾個(gè)屬性。person1中的[[prototype]]屬性不可被訪問(wèn),因此不能通過(guò)for-in循環(huán)枚舉出它。
object.keys()方法接收一個(gè)參數(shù),這個(gè)參數(shù)可以是原型對(duì)象,也可以是由構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象,返回一個(gè)包含所有可枚舉屬性的字符串?dāng)?shù)組。
例:
function Person(){}
Person.prototype.name = "huazhen";
Person.prototype.age = 22;
Person.prototype.job = "yaoniguan";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person1.prototype);
alert(keys); //name,age,job,sayName
var p1 = new Person();
p1.name = "heiheihei"
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //heiheihei,age
可以看出Object.keys()方法只返回其自身的屬性。如原型對(duì)象只返回原型對(duì)象中的屬性,對(duì)象實(shí)例也只返回對(duì)象實(shí)例自己創(chuàng)建的屬性,而不能返回繼承自原型對(duì)象的屬性。
5.更簡(jiǎn)單的原型語(yǔ)法
之前的例子,在構(gòu)造函數(shù)的原型對(duì)象中添加屬性和方法時(shí),都要在前面橋Person.prototype,而原型對(duì)象說(shuō)到底還是對(duì)象,只要是對(duì)象,那么就可以用對(duì)象字面量方法來(lái)創(chuàng)建
例:
function Person(){}
Person.prototype = {
name:"huazhen",
age:22,
job"yaoniguan",
sayName:function(){
alert(this.name);
}
}
這樣就減少了不必要的輸入,也可以更好的封裝原型。但是,這時(shí)的原型對(duì)象的constructor就不會(huì)指向Person構(gòu)造函數(shù)而是指向Object構(gòu)造函數(shù)
為什么會(huì)這樣???
當(dāng)我們創(chuàng)建Person構(gòu)造函數(shù)時(shí),就會(huì)同時(shí)自動(dòng)創(chuàng)建這個(gè)Person構(gòu)造函數(shù)的原型(prototype)對(duì)象,這個(gè)原型對(duì)象也自動(dòng)獲取了一個(gè)constructor屬性并指向Person構(gòu)造函數(shù),這個(gè)之前的圖示可以看的很清楚。之前我們用Person.prototype.name="huazhen",這種方式向原型對(duì)象添加屬性,并沒(méi)有本質(zhì)上的改變。然而,上述這種封裝性較好的方式,即使用對(duì)象字面量的方法,實(shí)際上是使用Object構(gòu)造函數(shù)的原型對(duì)象(對(duì)象字面量本質(zhì)即使用Object構(gòu)造函數(shù)創(chuàng)建新對(duì)象),注意,此時(shí)Person構(gòu)造函數(shù)的原型對(duì)象不再是之前的原型對(duì)象(而之前的原型對(duì)象的constructor屬性仍然指向Person構(gòu)造函數(shù)),這個(gè)原型對(duì)象和創(chuàng)建Person構(gòu)造函數(shù)時(shí)自動(dòng)生成的原型對(duì)象根本不一樣。即,對(duì)象字面量創(chuàng)建的原型對(duì)象的constructor屬性此時(shí)指向Object構(gòu)造函數(shù)。
例:
function Person(){}
Person.prototype = {
name:"huazhen",
age:22,
job"yaoniguan",
sayName:function(){
alert(this.name);
}
}
var person1 = new Person();
alert(Person.prototype.constructor == Person); //false
alert(Person.prototype.constructor == Object); //true
如果constructor的值真的很重要,可以將其設(shè)置回適當(dāng)?shù)闹担?/p>
function Person(){}
Person.prototype = {
constructor:Person, //設(shè)置constructor
name:"huazhen",
age:22,
job"yaoniguan",
sayName:function(){
alert(this.name);
}
}
注意:這種方式重設(shè)constructor屬性會(huì)導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true,而默認(rèn)模式下,原生的constructor屬性是不可枚舉的。但是可以用Object.defineProperty()修改
6.原型的動(dòng)態(tài)性
由于在原型中查找值的過(guò)程是一次搜索,因此對(duì)原型對(duì)象所做的任何修改都可以從實(shí)例上反應(yīng)出來(lái),即使先創(chuàng)建實(shí)例后修改原型也是這樣:
例:
var person = new Person();
Person.prototype.sayHi = function(){
alert("hi");
}
friend.sayHi(); //hi
原因:可以歸結(jié)為實(shí)例和原型之間松散的連接關(guān)系,當(dāng)盜用friend.sayHi()時(shí),首先首先會(huì)在實(shí)例中搜索名為sayHi的屬性,沒(méi)有找到,會(huì)繼續(xù)搜索原型。可以在原型中找到并返回。
但是如果重寫整個(gè)原型對(duì)象,情況就不同:調(diào)用構(gòu)造函數(shù)時(shí),回味實(shí)例添加一個(gè)指向最初原型的[[Prototype]]指針,而把原型修改為另一個(gè)對(duì)象就等于切斷了構(gòu)造函數(shù)和最初原型之間的聯(lián)系。實(shí)例中的指針僅指向原型,而不指向構(gòu)造函數(shù)
例:
function Person(){}
//注意
var friend = new Person();
Person.prototype = {
constructor:Person, //設(shè)置constructor
name:"huazhen",
age:22,
job"yaoniguan",
sayName:function(){
alert(this.name);
}
};
friend.sayName(); //error
重寫原型對(duì)象切斷了現(xiàn)有原型與任何之前已存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的仍然是最初的原型。
7.原生對(duì)象的原型
原型的重要性不僅體現(xiàn)在自定義類型方面,就連所有的原生的引用類型,都是使用這種模式創(chuàng)建的。所有原生引用類型(Object、Array、String等)都在其構(gòu)造函數(shù)的原型上定義了方法。例如在Array.prototype中可以找到sort()方法,而在String.prototype中可以找到substring()方法。不推薦在產(chǎn)品化的程序中修改原生對(duì)象的原型,這樣做可能導(dǎo)致命名沖突等問(wèn)題。
8.原型模式存在的問(wèn)題
回顧我們的問(wèn)題:其一,可以直接識(shí)別創(chuàng)建對(duì)象的類型;其二,解決工廠模式解決的創(chuàng)建大量相似對(duì)象時(shí)產(chǎn)生的代碼重復(fù)問(wèn)題;其三,解決構(gòu)造函數(shù)產(chǎn)生的封裝性不好的問(wèn)題。
其中第一個(gè)問(wèn)題已解決,通過(guò)構(gòu)造函數(shù)就可以看出函數(shù)類型。第二個(gè)問(wèn)題解決的不太好,它省略了為構(gòu)造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié),結(jié)果所有的實(shí)例在默認(rèn)情況下都將取得相同的默認(rèn)值,我們只能通過(guò)在實(shí)例上添加同名屬性來(lái)屏蔽原型中的屬性,這無(wú)疑會(huì)造成代碼重復(fù)的問(wèn)題。第三個(gè)問(wèn)題,封裝性還好。算是勉強(qiáng)解決了問(wèn)題。但是產(chǎn)生了額外的問(wèn)題,如下:
例:
function Person(){}
Person.prototype = {
constructor:Person, //設(shè)置constructor
name:"huazhen",
age:22,
job"yaoniguan",
friends:["xiaohong","xiaozhang"],
sayName:function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("xiaoming");
alert(person1.friends); //["xiaohong","xiaozhang","xiaoming"]
alert(person2.friends); //["xiaohong","xiaozhang","xiaoming"]
產(chǎn)生的問(wèn)題:由于friends數(shù)組存在于Person.prototype而非person1中,所以剛剛提到的修改也會(huì)通過(guò)person2.firends(與person1.friends指向同一個(gè)數(shù)組)反應(yīng)出來(lái)。
五、組合使用自定義構(gòu)造函數(shù)模式和原型模式
原型模式存在連個(gè)最大的問(wèn)題:
- 問(wèn)題1:由于沒(méi)有為構(gòu)造函數(shù)創(chuàng)建對(duì)象實(shí)例時(shí)傳遞初始化參數(shù),所有的實(shí)例在默認(rèn)情況下獲取相同的默認(rèn)值;
- 問(wèn)題2:對(duì)于原型對(duì)象中包含引用類型的屬性,在某一個(gè)實(shí)例中修改引用類型的值,會(huì)牽扯到其他的實(shí)例;
組合使用時(shí):構(gòu)造函數(shù)應(yīng)用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性。
例:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friend = ["xiaohong","xiaozhang"];
}
Person.prototype = {
constructor:Person;
sayName:function(){
alert(this.name);
}
}
var person1 = new Person("huazhen",22,"yaoniguan");
var person2 = new Person("heiheihei",25,"hehe");
person1.friends.push("xiaoming");
alert(person1.friends); //["xiaohong","xiaozhang","xiaoming"]
alert(person2.friends); //["xiaohong","xiaozhang"]
alert(person1.sayName == person2.sayName); //true
組合使用構(gòu)造函數(shù)模式和原型模式解決的問(wèn)題:
- 解決了Object構(gòu)造函數(shù)和對(duì)象字面量方法在創(chuàng)建大量對(duì)象時(shí)造成的代碼重復(fù)問(wèn)題(因?yàn)橹灰趧?chuàng)建對(duì)象時(shí)向構(gòu)造函數(shù)傳遞參數(shù)即可)。
- 解決了工廠模式產(chǎn)生的無(wú)法識(shí)別對(duì)象類型的問(wèn)題(因?yàn)檫@里通過(guò)構(gòu)造函數(shù)即可獲知對(duì)象類型)。
- 解決了自定義構(gòu)造函數(shù)模式封裝性較差的問(wèn)題(這里全部都被封裝)。
- 解決了原型模式的兩個(gè)問(wèn)題:所有實(shí)例共享相同的屬性以及包含引用類型的數(shù)組在實(shí)例中修改時(shí)會(huì)影響原型對(duì)象中的數(shù)組。
綜上所述,組合使用構(gòu)造函數(shù)模式和原型模式可以說(shuō)是非常完美了。
六、動(dòng)態(tài)原型模式、寄生構(gòu)造模式、穩(wěn)妥構(gòu)造函數(shù)模式
實(shí)際上,組合使用構(gòu)造函數(shù)模式和原型模式已經(jīng)非常完美了,接下來(lái)的三種模式都是在特定情況下使用的,以便解決更多的問(wèn)題。
1.動(dòng)態(tài)原型模式
本質(zhì)上通過(guò)檢測(cè)某個(gè)應(yīng)該存在的方法是否存在或有效,來(lái)決定是否初始化原型。如下所示:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
var person = new Person("huazhen",22,"yaoniguan"); //使用new調(diào)用構(gòu)造函數(shù)并創(chuàng)建一個(gè)實(shí)例對(duì)象
person.sayName(); //huazhen
alert(person.job); //yaoniguan
這里先聲明了一個(gè)構(gòu)造函數(shù),然后當(dāng)使用new操作符調(diào)用構(gòu)造函數(shù)創(chuàng)建實(shí)例對(duì)象時(shí)進(jìn)入了構(gòu)造函數(shù)的函數(shù)執(zhí)行環(huán)境,開(kāi)始檢測(cè)對(duì)象的sayName是否存在或是否是一個(gè)函數(shù),如果不是,就使用原型修改的方式向原型中添加sayName函數(shù)。且由于原型的動(dòng)態(tài)性,這里所做的修改可以在所有實(shí)例中立即得到反映。值得注意的是,在使用動(dòng)態(tài)原型模式時(shí),不能使用對(duì)象字面量重寫原型,否則,在建立了實(shí)例的情況下重寫原型會(huì)導(dǎo)致切斷實(shí)例和新原型的聯(lián)系。
2.寄生構(gòu)造函數(shù)模式
寄生構(gòu)造函數(shù)模式:
例:
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 person = new Person("huazhen",22,"yaoniguan");
person.sayName(); //huazhen
寄生構(gòu)造函數(shù)的特點(diǎn)如下:
- 聲明一個(gè)構(gòu)造函數(shù),在構(gòu)造函數(shù)內(nèi)部創(chuàng)建對(duì)象,最后返回該對(duì)象,因此這個(gè)函數(shù)的作用僅僅是封裝創(chuàng)建對(duì)象的代碼。
- 可以看出,這種方式除了在創(chuàng)建對(duì)象的時(shí)候使用了構(gòu)造函數(shù)的模式(函數(shù)名大寫,用new關(guān)鍵字調(diào)用)以外與工廠模式一模一樣。
- 構(gòu)造函數(shù)在不返回值的情況下,默認(rèn)會(huì)返回新對(duì)象實(shí)例,而通過(guò)構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句,可以重寫調(diào)用構(gòu)造函數(shù)時(shí)返回的值。
假設(shè)我們想要?jiǎng)?chuàng)建一個(gè)具有額外方法的特殊數(shù)組,通過(guò)改變Array構(gòu)造函數(shù)的原型對(duì)象是可以實(shí)現(xiàn)的,但是前面提到過(guò),這種方式可能會(huì)導(dǎo)致后續(xù)的命名沖突等一系列問(wèn)題,是不推薦的。而寄生構(gòu)造函數(shù)就能很好的解決這一問(wèn)題。如下所示:
例:
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPipedString = function(){
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.toPipedString()); //red|blue|green
這里沒(méi)有改變Array構(gòu)造函數(shù)的原型對(duì)象,又完成了添加Array方法的目的。
3.穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對(duì)象是指這沒(méi)有公共屬性,而且方法也不引用this的對(duì)象。穩(wěn)妥對(duì)象適合在安全的環(huán)境中使用,或者在防止數(shù)據(jù)被其他應(yīng)用程序改動(dòng)時(shí)使用。
例:
function Person(name,age,job){
var o = new Object();
o.sayName = function(){
alert(name);
};
return o;
}
var person = Person("huazhen",22,"yaoniguan");
person.sayName(); //huazhen
可以看出來(lái),這種模式和寄生構(gòu)造函數(shù)模式非常相似,只是:
- 新創(chuàng)建對(duì)象的實(shí)例方法不用this。
- 不用new操作符調(diào)用構(gòu)造函數(shù)(由函數(shù)名的首字母大寫可以看出它的確是一個(gè)構(gòu)造函數(shù))。
注意:變量person中保存的是一個(gè)穩(wěn)妥對(duì)象,除了調(diào)用sayName()方法外沒(méi)有別的方式可以訪問(wèn)其數(shù)據(jù)成員
例:
alert(person.name); //undefined
alert(person.age); //undefined
【完結(jié)】