下面小編就為大家?guī)?lái)一篇淺析在javascript中創(chuàng)建對(duì)象的各種模式。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。
最近在看《javascript高級(jí)程序設(shè)計(jì)》(第二版)
javascript中對(duì)象的創(chuàng)建
•工廠模式
•構(gòu)造函數(shù)模式
•原型模式
•結(jié)合構(gòu)造函數(shù)和原型模式
•原型動(dòng)態(tài)模式
面向?qū)ο蟮恼Z(yǔ)言大都有一個(gè)類的概念,通過(guò)類可以創(chuàng)建多個(gè)具有相同方法和屬性的對(duì)象。雖然從技術(shù)上講,javascript是一門面向?qū)ο蟮恼Z(yǔ)言,但是javascript沒(méi)有類的概念,一切都是對(duì)象。任意一個(gè)對(duì)象都是某種引用類型的實(shí)例,都是通過(guò)已有的引用類型創(chuàng)建;引用類型可以是原生的,也可以是自定義的。原生的引用類型有:Object、Array、Data、RegExp、Function。 !引用類型就是一種數(shù)據(jù)結(jié)構(gòu),將數(shù)據(jù)和功能組織在一起,通常被稱為類。 缺乏類概念的javascript中,需要解決的問(wèn)題就是如何高效的創(chuàng)建對(duì)象。
1.1.0.創(chuàng)建對(duì)象的一般方法
var person = {}; //對(duì)象字面量表示,等同于var person = new Objcect();
person.name = 'evansdiy';
person.age = '22';
person.friends = ['ajiao','tiantian','pangzi'];
person.logName = function() {
console.log(this.name);
}
基于Object引用類型,創(chuàng)建了一個(gè)對(duì)象,該對(duì)象包含四個(gè)屬性,其中一個(gè)為方法。如果需要很多類似person的實(shí)例,那就會(huì)有許多重復(fù)的代碼。
1.1.1.工廠模式 [top]
通過(guò)一個(gè)可以包含了對(duì)象細(xì)節(jié)的函數(shù)來(lái)創(chuàng)建對(duì)象,然后返回這個(gè)對(duì)象。
function person(name,age,friends) {
var o = {
name: name,
age: age,
friends: friends,
logName: function() {
console.log(this.name);
}
};
return o;
}
var person1 = person('Evansdiy','22',['ajiao','tiantian','pangzi']);
每次調(diào)用person函數(shù),都會(huì)通過(guò)該函數(shù)內(nèi)部的對(duì)象o創(chuàng)建新的對(duì)象,然后返回,除此之外,這個(gè)為了創(chuàng)建新對(duì)象而存在的內(nèi)部對(duì)象o沒(méi)有其他的用途。另外,無(wú)法判斷工廠模式創(chuàng)建的對(duì)象的類型。
1.1.2.構(gòu)造函數(shù)模式 [top]
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.logName = function() {
console.log(this.name);
}
}
//通過(guò)new操作符創(chuàng)建Person的實(shí)例
var person1 = new Person('boy-a','22','worker');
var person2 = new Person('girl-b','23','teacher');
person1.logName(); //boy-a
person2.logName(); //girl-a
對(duì)比工廠模式,可以發(fā)現(xiàn),這里并不需要?jiǎng)?chuàng)建中間對(duì)象,沒(méi)有return。另外,可以將構(gòu)造函數(shù)的實(shí)例標(biāo)識(shí)為一種特定的類型,這就解決了對(duì)象識(shí)別的問(wèn)題(通過(guò)檢查實(shí)例的constructor屬性,或利用instanceof操作符檢查該實(shí)例是否通過(guò)某個(gè)構(gòu)造函數(shù)創(chuàng)建)。
console.log(person1.constructor == Person);//constructor位于構(gòu)造函數(shù)原型中,并指向構(gòu)造函數(shù),結(jié)果為true
console.log(person1 instanceof Person);//通過(guò)instanceof操作符,判斷person1是否為構(gòu)造函數(shù)Person的實(shí)例但構(gòu)造函數(shù)模式也有自己的問(wèn)題,實(shí)際上,logName方法在每個(gè)實(shí)例上都會(huì)被重新創(chuàng)建一次,需要注意的是,通過(guò)實(shí)例化創(chuàng)建的方法且并不相等,以下代碼將會(huì)得到false:
console.log(person1.logName == person2.logName);//false我們可以將方法移到構(gòu)造器外部(變?yōu)槿趾瘮?shù))來(lái)解決這個(gè)問(wèn)題:
function logName() {
console.log(this.name);
}
function logAge() {
console.log(this.age);
}
但是,在全局下創(chuàng)建的全局函數(shù)實(shí)際上只能被經(jīng)由Person創(chuàng)建的實(shí)例調(diào)用,這就有點(diǎn)名不副實(shí)了;如果方法很多,還需要逐一定義,缺少封裝性。
1.1.3.原型模式 [top]
javascript中的每一個(gè)函數(shù)都包含一個(gè)指向prototype屬性的指針(大部分瀏覽器可以通過(guò)內(nèi)部屬性__proto__訪問(wèn)),prototype屬性是一個(gè)對(duì)象,其中包含了由某種引用類型創(chuàng)建的所有實(shí)例共享的屬性和方法。
function Person() {}
Person.name = 'evansdiy';
Person.prototype.friends = ['ajiao','jianjian','pangzi'];
Person.prototype.logName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.logName();//'evansdiy'
以上代碼做了這幾件事情:
1.定義了一個(gè)構(gòu)造函數(shù)Person,Person函數(shù)自動(dòng)獲得一個(gè)prototype屬性,該屬性默認(rèn)只包含一個(gè)指向Person的constructor屬性;
2.通過(guò)Person.prototype添加三個(gè)屬性,其中一個(gè)作為方法;
3.創(chuàng)建一個(gè)Person的實(shí)例,隨后在實(shí)例上調(diào)用了logName()方法。
這里需要注意的是logName()方法的調(diào)用過(guò)程:
1.在person1實(shí)例上查找logName()方法,發(fā)現(xiàn)沒(méi)有這個(gè)方法,于是追溯到person1的原型
2.在person1的原型上查找logame()方法,有這個(gè)方法,于是調(diào)用該方法 基于這樣一個(gè)查找過(guò)程,我們可以通過(guò)在實(shí)例上定義原型中的同名屬性,來(lái)阻止該實(shí)例訪問(wèn)原型上的同名屬性,需要注意的是,這樣做并不會(huì)刪除原型上的同名屬性,僅僅是阻止實(shí)例訪問(wèn)。
var person2 = new Person();
person2.name = 'laocai';如果我們不再需要實(shí)例上的屬性時(shí),可以通過(guò)delete操作符刪除。
delete person2.name;利用for-in循環(huán)枚舉出實(shí)例可以訪問(wèn)到的所有屬性(不論該屬性存在于實(shí)例或是原型中):
for(i in person1) {
console.log(i);
}
同時(shí),也可以利用hasOwnProperty()方法判斷某個(gè)屬性到底存在于實(shí)例上,還是存在于原型中,只有當(dāng)屬性存在于實(shí)例中,才會(huì)返回true:
console.log(person1.hasOwnProperty('name'));//true!hasOwnProperty來(lái)自O(shè)bject的原型,是javascript中唯一一個(gè)在處理屬性時(shí)不查找原型鏈的方法。[via javascript秘密花園] 另外,也可以通過(guò)同時(shí)使用in操作符和hasOwnProperty()方法來(lái)判斷某個(gè)屬性存在于實(shí)例中還是存在于原型中:
console.log(('friends' in person1) && !person1.hasOwnProperty('friends'));先判斷person1是否可以訪問(wèn)到friends屬性,如果可以,再判斷這個(gè)屬性是否存在于實(shí)例當(dāng)中(注意前面的!),如果不存在于實(shí)例中,就說(shuō)明這個(gè)屬性存在于原型中。 前面提到,原型也是對(duì)象,所以我們可以用對(duì)象字面量表示法書寫原型,之前為原型添加代碼的寫法可以修改為:
Person.prototype = {
name: 'evansdiy',
friends: ['ajiao','jianjian','pangzi'],
logName: function() {
console.log(this.name);
}
}
由于對(duì)象字面量語(yǔ)法重寫了整個(gè)prototype原型,原先創(chuàng)建構(gòu)造函數(shù)時(shí)默認(rèn)取得的constructor屬性會(huì)指向Object構(gòu)造函數(shù):
//對(duì)象字面量重寫原型之后
console.log(person1.constructor);//Object不過(guò),instanceof操作符仍會(huì)返回希望的結(jié)果:
//對(duì)象字面量重寫原型之后
console.log(person1 instanceof Person);//true當(dāng)然,可以在原型中手動(dòng)設(shè)置constructor的值來(lái)解決這個(gè)問(wèn)題。
Person.prototype = {
constructor: Person,
......
}
如果在創(chuàng)建對(duì)象實(shí)例之后修改原型對(duì)象,那么對(duì)原型的修改會(huì)立即在所有對(duì)象實(shí)例中反映出來(lái):
function Person() {};
var person1 = new Person();
Person.prototype.name = 'evansdiy';
console.log(person1.name);//'evansdiy'
實(shí)例和原型之間的連接僅僅是一個(gè)指針,而不是一個(gè)原型的拷貝,在原型實(shí)際上是一次搜索過(guò)程,對(duì)原型對(duì)象的所做的任何修改都會(huì)在所有對(duì)象實(shí)例中反映出來(lái),就算在創(chuàng)建實(shí)例之后修改原型,也是如此。 如果在創(chuàng)建對(duì)象實(shí)例之后重寫原型對(duì)象,情況又會(huì)如何?
function Person() {};
var person1 = new Person1();//創(chuàng)建的實(shí)例引用的是最初的原型
//重寫了原型
Person.prototype = {
friends: ['ajiao','jianjian','pangzi']
}
var person2 = new Person();//這個(gè)實(shí)例引用新的原型
console.log(person2.friends);
console.log(person1.friends);
以上代碼在執(zhí)行到最后一行時(shí)會(huì)出現(xiàn)未定義錯(cuò)誤,如果我們用for-in循環(huán)枚舉person1中的可訪問(wèn)屬性時(shí),會(huì)發(fā)現(xiàn),里頭空無(wú)一物,但是person2卻可以訪問(wèn)到原型上的friends屬性。 !重寫原型切斷了現(xiàn)有原型與之前創(chuàng)建的所有對(duì)象實(shí)例的聯(lián)系,之前創(chuàng)建的對(duì)象實(shí)例的原型還在,只不過(guò)是舊的。
//創(chuàng)建person1時(shí),原型對(duì)象還未被重寫,因此,原型對(duì)象中的constructor還是默認(rèn)的Person()
console.log(person1.constructor);//Person()
//但是person2的constructor指向Object()
console.log(person2.constructor);//Object()需要注意的是,原型模式忽略了為構(gòu)造函數(shù)傳遞參數(shù)的過(guò)程,所有的實(shí)例都取得相同的屬性值。同時(shí),原型模式還存在著一個(gè)很大的問(wèn)題,就是原型對(duì)象中的引用類型值會(huì)被所有實(shí)例共享,對(duì)引用類型值的修改,也會(huì)反映到所有對(duì)象實(shí)例當(dāng)中。
function Person() {};
Person.prototype = {
friends: ['ajiao','tiantian','pangzi']
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('laocai');
console.log(person2.friends);//['ajiao','tiantian','pangzi','laocai']
修改person1的引用類型值friends,意味著person2中的friends也會(huì)發(fā)生變化,實(shí)際上,原型中保存的friends實(shí)際上只是一個(gè)指向堆中friends值的指針(這個(gè)指針的長(zhǎng)度是固定的,保存在棧中),實(shí)例通過(guò)原型訪問(wèn)引用類型值時(shí),也是按指針訪問(wèn),而不是訪問(wèn)各自實(shí)例上的副本(這樣的副本并不存在)。
1.1.4.結(jié)合構(gòu)造函數(shù)和原型模式創(chuàng)建對(duì)象 [top]
結(jié)合構(gòu)造函數(shù)和原型模式的優(yōu)點(diǎn),彌補(bǔ)各自的不足,利用構(gòu)造函數(shù)傳遞初始化參數(shù),在其中定義實(shí)例屬性,利用原型定義公用方法和公共屬性,該模式應(yīng)用最為廣泛。
function Person(name,age) {
this.name = name;
this.age = age;
this.friends = ['ajiao','jianjian','pangzi'];
}
Person.prototype = {
constructor: Person,
logName: function() {
console.log(this.name);
}
}
var person1 = new Person('evansdiy','22');
var person2 = new Person('amy','21');
person1.logName();//'evansdiy'
person1.friends.push('haixao');
console.log(person2.friends.length);//3
1.1.5.原型動(dòng)態(tài)模式 [top]
原型動(dòng)態(tài)模式將需要的所有信息都封裝到構(gòu)造函數(shù)中,通過(guò)if語(yǔ)句判斷原型中的某個(gè)屬性是否存在,若不存在(在第一次調(diào)用這個(gè)構(gòu)造函數(shù)的時(shí)候),執(zhí)行if語(yǔ)句內(nèi)部的原型初始化代碼。
function Person(name,age) {
this.name = name;
this.age = age;
if(typeof this.logName != 'function') {
Person.prototype.logName = function() {
console.log(this.name);
};
Person.prototype.logAge = function() {
console.log(this.age);
};
};
}
var person1 = new Person('evansdiy','22');//初次調(diào)用構(gòu)造函數(shù),此時(shí)修改了原型
var person2 = new Person('amy','21');//此時(shí)logName()方法已經(jīng)存在,不會(huì)再修改原型
需要注意的是,該模式不能使用對(duì)象字面量語(yǔ)法書寫原型對(duì)象(這樣會(huì)重寫原型對(duì)象)。若重寫原型,那么通過(guò)構(gòu)造函數(shù)創(chuàng)建的第一實(shí)例可以訪問(wèn)的原型對(duì)象不會(huì)包含if語(yǔ)句中的原型對(duì)象屬性。
function Person(name,age) {
this.name = name;
this.age = age;
if(typeof this.logName != 'function') {
Person.prototype = {
logName: function() {
console.log(this.name);
},
logAge: function() {
console.log(this.Age);
}
}
};
}
var person1 = new Person('evansdiy','22');
var person2 = new Person('amy','21');
person2.logName();//'amy'
person1.logName();//logName()方法不存在
需要說(shuō)明的是,各模式都有自己的應(yīng)用場(chǎng)景,無(wú)所謂優(yōu)劣。
以上這篇淺析在javascript中創(chuàng)建對(duì)象的各種模式就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考