您好,登錄后才能下訂單哦!
小編給大家分享一下JavaScript中怎么創(chuàng)建對象,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
ECMA-262把對象定義為:”無需屬性的集合,其屬性可以包含基本值、對象或者函數(shù)?!眹栏駚碇v,這就相當(dāng)于說明對象是一組沒有特定順序的值。對象的每個屬性或方法都有一個名字,而每個名字都映射到一個值。正因為這樣,我們可以把ECMAScript的對象想象成散列表:無非就是一組名對值,其中值可以是數(shù)據(jù)或函數(shù)。
創(chuàng)建自定義對象最簡單的方式就是創(chuàng)建一個Object的實例,然后再為他添加屬性和方法,如下所示:
var person = new Object();、person.name = "liubei";person.age = 29; person.job = "shayemuyou"; person.sayName = function(){ alert(this.name); }
上面的例子創(chuàng)建了一個名為person的對象,并為他添加了三個屬性和一個方法。其中sayName()方法用于顯示name屬性,this.name將被解析為person.name,早期的開發(fā)人員經(jīng)常使用這個模式來創(chuàng)建對象,后來對象字面量的方法成了創(chuàng)建對象的首選模式,上面的例子用對象字面量的語法可以寫成如下這樣:
var person = { name:"liubei", age:29, job:"shayemuyou", sayName:function(){ alert(this.name); } }
這個例子中的person對象和前面的對象是一樣的,都有相同的屬性和方法。
雖然Object構(gòu)造函數(shù)或者對象字面量的方法都可以用來創(chuàng)建單個對象,但是這些方法有個明顯的缺點:使用同一個接口創(chuàng)建很多對象,會產(chǎn)生大量的重復(fù)代碼。為了解決這個方法,人們開始使用工廠模式的一種變體。
一、工廠模式
工廠模式是軟件工程領(lǐng)域一種廣為人知的設(shè)計模式,這種模式抽象了創(chuàng)建具體對象的過程。考慮到ECMAScript中無法創(chuàng)建類,開發(fā)人員就發(fā)明了一種函數(shù),用函數(shù)來封裝以特定接口創(chuàng)建對象的細節(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("wei",25,"software"); var person2 = createPerson("bu",25,"software");
函數(shù)createPerson()能夠根據(jù)接受的參數(shù)來構(gòu)建一個包含所有必要信息的Person對象??梢远啻握{(diào)用這個函數(shù),每次都會返回一個包含三個屬性一個方法的對象。工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但卻沒有解決對象識別的問題,即怎么樣知道這是哪個對象類型。
二、構(gòu)造函數(shù)模式
像Array、Object這樣的原生構(gòu)造函數(shù),在運行時會自動出現(xiàn)在執(zhí)行環(huán)境中。此外,我們可以創(chuàng)建自定義個構(gòu)造函數(shù),從而定義自定義類型的屬性和方法。例如,我們可以使用構(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("wei",25,"software"); var person2 = new Person("bu",25,"software");
在這個例子中,Person()函數(shù)取代了createPerson()函數(shù),我們注意到Person()與createPerson()的不同之處在于:
沒有顯式的創(chuàng)建對象
直接將屬性和方法賦值給this對象
沒有return語句
此外,還應(yīng)該注意到函數(shù)名Person使用的是大寫字母P。按照慣例,構(gòu)造函數(shù)始終都應(yīng)該以一個大寫字母開頭,而非構(gòu)造函數(shù)則應(yīng)該以一個小寫字母開頭。這個做法借鑒了其他OO語言,主要是為了區(qū)別于ECMAScript中的其他函數(shù)。因為構(gòu)造函數(shù)本身也是函數(shù),只不過可以創(chuàng)建對象而已。
要創(chuàng)建一個Person實例,必須使用new操作符。以上這種方式會經(jīng)過以下四個步驟:
1、創(chuàng)建一個新對象
2、將構(gòu)造函數(shù)的作用域賦給新對象(因此this指向這個新對象)
3、執(zhí)行構(gòu)造函數(shù)中的代碼
4、返回新對象
在前面例子的最后,person1和person2分別保存著Person的一個不同的實例。這兩個對象都有一個constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。如下:
console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true
對象的constructor屬性最初是用來標(biāo)識對象類型的。但是,提到檢測對象類型,還是instanceof操作符比較可靠一些。我們在這個例子中創(chuàng)建的對象都是Object對象的實例,也是Person對象的實例,這一點通過instanceof操作符可以驗證。
console.log(person1 instanceof Object); //trueconsole.log(person1 instanceof Person); //trueconsole.log(person2 instanceof Object); //trueconsole.log(person2 instanceof Person); //true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將他的實例標(biāo)識為一種特定的類型;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。在這個例子中,person1和person2之所以同是Object的實例,是因為所有的對象都繼承自O(shè)bject。
構(gòu)造函數(shù)的主要問題,就是每個方法都要在實例上重新創(chuàng)建一遍,造成內(nèi)存浪費。在前面的例子中,person1和person2都有一個名為sayName()的方法,但是兩個方法不是同一Function的實例。不要忘了ECMAScript中的函數(shù)也是對象,因此每定義一個函數(shù),也就是實例化了一個對象,從邏輯角度講,此時的構(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ù)在邏輯上是等價的 }
從這個角度來看構(gòu)造函數(shù),更容易看明白每個Person實例都會包含一個不同的Function實例的本質(zhì)。說明白些,會導(dǎo)致不同的作用域鏈和標(biāo)識符解析,但是創(chuàng)建Function新實例的機制仍然是相同的。因此,不同實例上的同名函數(shù)是不相等的,以下代碼可以證實這一點。
alert(person1.sayName == person2.sayName); //false
然而,創(chuàng)建兩個完成同樣任務(wù)的Function實例的確沒有必要;況且有this對象在,根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定的對象上。因此,可以像下面這樣,通過把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來解決這個問題。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); }
這樣做解決了多個函數(shù)解決相同問題的問題,但是有產(chǎn)生了新的問題,在全局作用域中實際上只被某個對象調(diào)用,這讓全局對象有點名不副實。更讓人無法接受的是:如果對象需要定義很多方法,那么就要定義很多全局函數(shù),于是我們這個自定義的引用類型就絲毫沒有封裝性可言了。好在這些問題可以使用原型模式來解決。
三、原型模式
我們創(chuàng)建的每個函數(shù)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。使用原型對象的實例就是讓所有實例共享它所包含的屬性和方法。換句話說,不必在構(gòu)造函數(shù)中定義對象的實例信息,而是可以將這些信息直接添加到原型對象中,如下所示:
function Person(){ } Person.prototype.name = "wei"; Person.prototype.age = 27; Person.prototype.job = "Software"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); person1.sayName(); //"wei" var person2 = new Person(); person2.sayName(); //"wei" alert(person1.sayName == person2.sayName);
在此,我們將sayName()方法和所有的屬性直接添加在了Person的prototype屬性中,構(gòu)造函數(shù)變成了空函數(shù)。即便如此,我們?nèi)匀豢梢酝ㄟ^構(gòu)造函數(shù)來創(chuàng)建新對象,而且新對象還會具有相同的屬性和方法。但是與構(gòu)造函數(shù)不同的是,新對象的這些屬性和方法是由所有實例共享的。換句話說,person1和person2訪問的都是同一組屬性和同一個sayName()函數(shù)。要理解原型模式的工作原理,就必須先理解ECMAScript中原型對象的性質(zhì)。
原型對象的本性由于篇幅太長將會在下一章節(jié)詳細分析。上面我們說了原型模式的好處,接下來我們來看一下原型模式的缺點。原型模式省略了為構(gòu)造函數(shù)傳遞參數(shù)的這一環(huán)節(jié),結(jié)果所有實例在默認情況下都具有相同的屬性值。這會在某些程度上帶來一種不便,這并不是原型模式最大的問題,因為如果我們想為一個通過原型模式創(chuàng)建的對象添加屬性時,添加的這個屬性就會屏蔽原型對象的保存的同名屬性。換句話說,就是添加的這個屬性會阻止我們?nèi)ピL問原型中的屬性,但并不會改變原型中的屬性。
原型模式最大的問題是由其共享的本質(zhì)所導(dǎo)致的。原型中所有的屬性被很多實例共享,這種共享對函數(shù)非常合適,對包含基本值的屬性也說的過去,但是對引用類型的屬性值來說問題就比較突出了,下面我們來看一個例子:
function Person(){ } Person.prototype = { constructor:Person, name:"wei", age:29, friends:["乾隆","康熙"], sayName:function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("嬴政"); console.log(person1.friends); //["乾隆","康熙","嬴政"] console.log(person2.friends); //["乾隆","康熙","嬴政"] console.log(person1.friends === person2.friends); //true
上面的例子中,Person.prototype對象有一個名為friends的屬性,該屬性包含一個字符串?dāng)?shù)組。然后創(chuàng)建了兩個Person的實例,接著修改person1.friends引用的數(shù)組,向數(shù)組中添加一個字符串,由于數(shù)組存在于Person.prototype中而不是person1中,所以person2.friends也會被修改。但是一般每個對象都是要有屬于自己的屬性的,所以我們很少看到有人單獨使用原型模式來創(chuàng)建對象。
四、組合使用構(gòu)造函數(shù)模式和原型模式
創(chuàng)建自定義類型最常見的方式就是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實例屬性,原型模式用于定義方法和共享的屬性。結(jié)果,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度的節(jié)省了內(nèi)存。另外,這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù);可謂是集兩種模式之長。下面的代碼重寫了前面的例子:
function Person(name, age){ this.name = name; this.age = age; this.friends = ["乾隆","康熙"]; } Person.prototype = { constructor:Person, sayName:function(){ alert(this.name); } } var person1 = new Person("wei",29); var person2 = new Person("bu",25); person1.friends.push("嬴政"); console.log(person1.friends); //["乾隆", "康熙", "嬴政"] console.log(person2.friends); //["乾隆", "康熙"] console.log(person1.friends === person2.friends); //false console.log(person1.sayName === person2.sayName); //true
在這個例子中,實例屬性都是在構(gòu)造函數(shù)中定義的,而由所有實例共享的屬性constructor和方法sayName()則是在原型中定義的。所以修改了person1.friends并不會改變person2.friends,因為他們分別引用了不同的數(shù)組。
這種構(gòu)造函數(shù)與原型模式混成的模式,是目前在ECMAScript中使用最廣泛、認同度最高的一種創(chuàng)建自定義類型的方法。可以說,這是用來定義引用的一種默認形式。
五、動態(tài)原型模式
有其他OO語言經(jīng)驗的開發(fā)人員在看到獨立的構(gòu)造函數(shù)和原型時,很可能會感到非常的困惑。動態(tài)原型模式就是用來解決這個問題的一個方案,它把所有的信息都封裝在了構(gòu)造函數(shù)中,而通過構(gòu)造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點。換句話說,可以通過檢查某個應(yīng)該存在的方法是否有效,來決定是否要初始化原型。來看一個例子:
function Person(name, age){ this.name = name; this.age = age; this.friends = ["乾隆","康熙"]; //注意if語句 if(typeof this.sayName!="function"){ Person.prototype.sayName = function(){ alert(this.name); } } } var person1 = new Person("wei",29); person1.friends.push("嬴政"); person1.sayName();
注意構(gòu)造函數(shù)代碼中的if語句,這里只在sayName()方法不存在的情況下才會將它添加到原型中。這斷代碼只有在第一次調(diào)用構(gòu)造函數(shù)的時候才會被執(zhí)行。此后,原型已經(jīng)被初始化,不需要再做什么修改。不過要記住,這里所做的修改能立即在所有實例中得到反映。因此,這種方法可以說確實非常完美。其中if語句檢查的是初始化之后應(yīng)該存在的任何方法和屬性–不必再用一大堆if來檢查每個屬性和方法,只檢查其中一個即可。對于采用這樣模式創(chuàng)建的對象,還可以使用instanceof操作符來確定他的類型。
注意:使用動態(tài)原型模式時,不能使用對象字面量重寫原型。如果在已經(jīng)創(chuàng)建了實例的情況下重寫原型,那么就會切斷現(xiàn)有的實例與新原型之間的聯(lián)系。
六、寄生構(gòu)造函數(shù)模式
通常,在上述幾種模式都不適合的情況下可以使用寄生構(gòu)造函數(shù)模式。這種模式的基本思想是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象,但從表面看,這個函數(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("wei",29,"banzhuan"); person.sayName(); //"wei"
在這個例子中,Person函數(shù)創(chuàng)建了一個對象,并以相應(yīng)的屬性和方法初始化該對象,然后返回了這個對象。除了使用new操作符把使用的包裝函數(shù)叫做構(gòu)造函數(shù)之外,這個模式和工廠模式并沒有多大的區(qū)別。構(gòu)造函數(shù)在不返回值的情況下,會默認返回新對象的實例。而通過在構(gòu)造函數(shù)的末尾添加一個return語句,可以重寫調(diào)用構(gòu)造函數(shù)時返回的值。
這個模式可以在特殊的情況下來為對象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個具有額外方法的特殊數(shù)組。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個模式:
function SpecialArray(){ //創(chuàng)建數(shù)組 var values = new Array(); //添加值 values.push.apply(values,arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); } //返回數(shù)組 return values; } var colors = new SpecialArray("red","blue","green"); console.log(colors.toPipedString()); //red|blue|green
在這個例子中,我們創(chuàng)建了一個名為SpecialArray的構(gòu)造函數(shù)。在這個函數(shù)的內(nèi)部,首先創(chuàng)建了一個數(shù)組,然后push()方法初始化了數(shù)組的值。隨后又給數(shù)組實例添加了toPipedString()方法,用來返回以豎線分隔的數(shù)組值。最后將數(shù)組以函數(shù)的形式返回。接著,我們調(diào)用了SpecialArray構(gòu)造函數(shù),傳入了初始化的值,并調(diào)用了toPipedString()方法。
關(guān)于寄生構(gòu)造函數(shù)模式,有一點需要聲明:首先,返回的對象與構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型沒有任何關(guān)系;也就是說,構(gòu)造函數(shù)返回的對象與在構(gòu)造函數(shù)外部創(chuàng)建的對象沒有什么不同。為此,不能依賴instanceof操作符來確定對象的類型。由于存在這一的問題,我們建議在可以使用其他模式的情況下不要使用這種模式。
七、穩(wěn)妥構(gòu)造函數(shù)模式
道格拉斯·克拉克福德發(fā)明了JavaScript中的穩(wěn)妥對象這個概念。所謂穩(wěn)妥對象,是指沒有公共屬性,而且其方法也不引用this對象。穩(wěn)妥對象最適合在一些安全環(huán)境中(這些環(huán)境會禁止使用this和new),或者在防止數(shù)據(jù)被其他應(yīng)用程序改動時使用。穩(wěn)妥構(gòu)造函數(shù)遵循的與寄生構(gòu)造函數(shù)類似的模式,但又兩點不同:一是新創(chuàng)建對象的實例方法不引用this;二是不使用new操作符調(diào)用構(gòu)造函數(shù)。按照穩(wěn)妥構(gòu)造函數(shù)的要求,可以將前面的Person構(gòu)造函數(shù)重寫如下:
function Person(name, age, job){ //創(chuàng)建要返回的新對象 var o = new Object(); //可以在這里定義私有變量和函數(shù) //添加方法 o.sayName = function(){ alert(this.name); }; //返回對象 return o; }
注意,在以這種模式創(chuàng)建的對象中,除了使用sayName()方法之外,沒有其他辦法訪問name的值??梢韵裣旅媸褂梅€(wěn)妥的Person構(gòu)造函數(shù):
var person =Person("weiqi",22,"banzhuan"); person.sayName(); //weiqi
這樣,變量person中保存的是一個穩(wěn)妥對象,而除了sayName()方法外,沒有別的方式可以訪問其他數(shù)據(jù)成員。即使有其他代碼會給這個對象添加方法或數(shù)據(jù)成員,但也不可能有別的辦法訪問傳入到構(gòu)造函數(shù)中的原始數(shù)據(jù)。穩(wěn)妥構(gòu)造函數(shù)模式提供的這種安全性,使得他非常適合在某些安全執(zhí)行環(huán)境–例如,ADsafe(ADsafe)提供的環(huán)境下使用。
注意:與寄生構(gòu)造函數(shù)模式類似,使用穩(wěn)妥構(gòu)造函數(shù)模式創(chuàng)建的對象與構(gòu)造函數(shù)之間沒有什么關(guān)系,因此instanceof操作符對這種對象也沒有意義。
以上是“JavaScript中怎么創(chuàng)建對象”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。