javascript中創(chuàng)建對(duì)象

早期的博客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)題:


image

從這個(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é)】

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容