(注1:如果有問(wèn)題歡迎留言探討删窒,一起學(xué)習(xí)!轉(zhuǎn)載請(qǐng)注明出處颠毙,喜歡可以點(diǎn)個(gè)贊哦K谷搿)
(注2:更多內(nèi)容請(qǐng)查看我的目錄。)
1. 簡(jiǎn)介
學(xué)習(xí)JS蛀蜜,對(duì)象是一個(gè)繞不開(kāi)的話題刻两。本章將由淺入深探討對(duì)象的創(chuàng)建方法。
2. 創(chuàng)建單個(gè)對(duì)象的三種簡(jiǎn)單辦法
簡(jiǎn)單的對(duì)象創(chuàng)建方法滴某,主要用于創(chuàng)建單個(gè)的對(duì)象磅摹。但是不適合創(chuàng)建多個(gè)對(duì)象。就像是手工制品霎奢,不適合量產(chǎn)户誓。
2.1 對(duì)象字面量
最簡(jiǎn)單的對(duì)象創(chuàng)建方法,莫過(guò)于使用對(duì)象字面量了幕侠。
var doll = {
name: "Nicholas",
age: 29,
sayName: function () {
console.log(this.name);
}
};
這種寫(xiě)法簡(jiǎn)單干凈帝美,給人一種數(shù)據(jù)封裝的感覺(jué),是開(kāi)發(fā)人員最青睞的創(chuàng)建對(duì)象的方法橙依。要注意证舟,使用對(duì)象字面量的方法來(lái)定義對(duì)象,屬性名會(huì)自動(dòng)轉(zhuǎn)換成字符串窗骑。另外女责,一般地,對(duì)象字面量的最后一個(gè)屬性后的逗號(hào)將忽略创译,但在IE7-瀏覽器中導(dǎo)致錯(cuò)誤抵知,所以最后一個(gè)屬性后面最好不要帶逗號(hào)。
2.2 new + 內(nèi)置對(duì)象
最常用的就是new操作符后跟Object構(gòu)造函數(shù)。
var doll = new Object();
//如果不給構(gòu)造函數(shù)傳遞參數(shù)可以不加括號(hào) var doll= new Object;
doll.name = 'Nicholas';
doll.age = 29;
當(dāng)然其他內(nèi)置對(duì)象還有Array刷喜,Date残制,RegExp,F(xiàn)unction掖疮,基本包裝類型等初茶。比如:
var colors = new Array("red", "blue", "green");
2.3 Object.create()
ES5定義了一個(gè)名為Object.create()的方法,它創(chuàng)建一個(gè)新對(duì)象浊闪,第一個(gè)參數(shù)就是這個(gè)對(duì)象的原型恼布,第二個(gè)可選參數(shù)用以對(duì)對(duì)象的屬性進(jìn)行進(jìn)一步描述。
var o1 = Object.create({x:1,y:1}); // o1繼承了屬性x和y
console.log(o1.x); // 1
可以通過(guò)傳入?yún)?shù)null來(lái)創(chuàng)建一個(gè)沒(méi)有原型的新對(duì)象搁宾,但通過(guò)這種方式創(chuàng)建的對(duì)象不會(huì)繼承任何東西折汞,甚至不包括基礎(chǔ)方法。比如toString()和valueOf()盖腿。
var o2 = Object.create(null); // o2不繼承任何屬性和方法
var o1 = {};
console.log(Number(o1)); // NaN
console.log(Number(o2)); // Uncaught TypeError: Cannot convert object to primitive value
如果想創(chuàng)建一個(gè)普通的空對(duì)象(比如通過(guò){}或new Object()創(chuàng)建的對(duì)象)爽待,需要傳入Object.prototype。
var o3 = Object.create(Object.prototype); // o3和{}和new Object()一樣
var o1 = {};
console.log(Number(o1)); // NaN
console.log(Number(o3)); // NaN
Object.create()方法的第二個(gè)參數(shù)是屬性描述符翩腐。
var o1 = Object.create({z:3},{
x:{value:1,writable: false,enumerable:true,configurable:true},
y:{value:2,writable: false,enumerable:true,configurable:true}
});
console.log(o1.x,o1.y,o1.z); // 1 2 3
3. 創(chuàng)建多個(gè)對(duì)象的5種模式
雖然第二節(jié)中介紹的三種方法可以方便地創(chuàng)建一個(gè)對(duì)象鸟款,但這些方式有個(gè)明顯的缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼栗菜。例如欠雌,要?jiǎng)?chuàng)建10個(gè)具有name,age屬性和sayName方法的對(duì)象疙筹。利用對(duì)象字面量方法只能如下:
var doll1 = {
name: "Nicholas",
age: 29,
sayName: function () {
console.log(this.name);
}
};
var doll2 = {
name: "Nicholas",
age: 29,
sayName: function () {
console.log(this.name);
}
};
...
var doll10 = {
name: "Nicholas",
age: 29,
sayName: function () {
console.log(this.name);
}
};
為了解決這個(gè)問(wèn)題富俄,人們開(kāi)始使用工廠模式來(lái)創(chuàng)建對(duì)象。該模式抽象了創(chuàng)建具體對(duì)象的過(guò)程而咆,用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)霍比。這就好比原來(lái)是手工做一樣玩具,而現(xiàn)在你把流程程序化暴备,做成一臺(tái)機(jī)器悠瞬,用機(jī)器代替了人工。
3.1 工廠模式
function createDoll(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
alert(this.name);
};
return o;
}
var doll1 = createDoll("Nicholas", 29);
var doll2 = createDoll("Greg", 27);
createDoll函數(shù)能夠根據(jù)接收的參數(shù)來(lái)創(chuàng)建一個(gè)包含所有必要信息的Doll對(duì)象涯捻,可以無(wú)數(shù)次地調(diào)用這個(gè)函數(shù)浅妆,每次都能返回具有name,age屬性和sayName方法的對(duì)象障癌。
工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問(wèn)題凌外,但沒(méi)有解決對(duì)象識(shí)別的問(wèn)題,因?yàn)槭褂迷撃J讲](méi)有給出對(duì)象的類型涛浙。你并不知道doll1和doll2都是createDoll創(chuàng)建出來(lái)的對(duì)象康辑,你甚至不知道它們是不是一類東西摄欲。或者說(shuō)疮薇,你雖然用機(jī)器做出了玩具胸墙,但是你并不知道這是同一個(gè)機(jī)器造出的同一類玩具。
那怎么辦呢按咒?當(dāng)然是品牌化了迟隅。
3.2 構(gòu)造函數(shù)模式
前面說(shuō)過(guò),你可以用原生構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象胖齐。同樣玻淑,你也可以通過(guò)創(chuàng)建自定義的構(gòu)造函數(shù)嗽冒,來(lái)定義自定義對(duì)象類型的屬性和方法呀伙。創(chuàng)建自定義的構(gòu)造函數(shù)意味著可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型,而這正是構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方添坊。該模式?jīng)]有顯式地創(chuàng)建對(duì)象剿另,直接將屬性和方法賦給了this對(duì)象,且沒(méi)有return語(yǔ)句贬蛙。
function Doll(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}
var doll1 = new Doll("Nicholas", 29);
var doll2 = new Doll("Greg", 27);
利用這種方法雨女,我們給Doll定義了品牌‘Doll’,doll1和doll2都是Doll對(duì)象的實(shí)例阳准,當(dāng)然其本身也是Object的實(shí)例氛堕,Object本身就是最大的品牌。如下:
alert(doll1 instanceof Object); // true
alert(doll1 instanceof Doll); // true
alert(doll2 instanceof Object); // true
alert(doll2 instanceof Doll); // true
注意野蝇,構(gòu)造函數(shù)模式與工廠模式的區(qū)別:
- 沒(méi)有顯示地創(chuàng)建對(duì)象讼稚。
- 直接將屬性和方法賦給了this對(duì)象。
- 沒(méi)有return绕沈。
此外锐想,要使用構(gòu)造函數(shù)模式創(chuàng)建對(duì)象,必須使用new操作符乍狐。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際會(huì)經(jīng)歷以下步驟:
- 創(chuàng)建一個(gè)新對(duì)象赠摇。
- 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)(參考JS入門難點(diǎn)解析7-this)。
- 執(zhí)行函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)浅蚪。
- 返回新對(duì)象藕帜。
那么構(gòu)造函數(shù)模式的缺點(diǎn)是什么呢?使用構(gòu)造函數(shù)的主要問(wèn)題是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍惜傲,創(chuàng)建多個(gè)完成相同任務(wù)的方法完全沒(méi)有必要洽故,浪費(fèi)內(nèi)存空間。那么操漠,最簡(jiǎn)單的辦法收津,把函數(shù)方法定義在外部不就行了么饿这。
3.2.1 構(gòu)造函數(shù)拓展模式
在構(gòu)造函數(shù)模式的基礎(chǔ)上,把方法定義轉(zhuǎn)移到構(gòu)造函數(shù)外部撞秋,可以解決方法被重復(fù)創(chuàng)建的問(wèn)題长捧。
function Doll(name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
};
var doll1 = new Doll("Nicholas", 29);
var doll2 = new Doll("Greg", 27);
但是,問(wèn)題又來(lái)了吻贿。在全局作用域中定義的函數(shù)實(shí)際上只能被某個(gè)對(duì)象調(diào)用串结,這讓全局作用域有點(diǎn)名不副實(shí)。而且舅列,如果對(duì)象需要定義很多方法肌割,就要定義很多全局函數(shù),嚴(yán)重污染全局空間帐要,這個(gè)自定義的引用類型沒(méi)有封裝性可言了把敞。
當(dāng)然構(gòu)造函數(shù)不止前述所說(shuō),還有以下兩個(gè)變種榨惠。
3.2.2 寄生構(gòu)造函數(shù)模式
該模式的基本思想是創(chuàng)建一個(gè)函數(shù)奋早,該函數(shù)的作用僅僅是封裝創(chuàng)建對(duì)象的代碼,然后再返回新創(chuàng)建的對(duì)象赠橙。該模式是工廠模式和構(gòu)造函數(shù)模式的結(jié)合耽装。
寄生構(gòu)造函數(shù)模式與構(gòu)造函數(shù)模式有相同的問(wèn)題,每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍期揪,創(chuàng)建多個(gè)完成相同任務(wù)的方法完全沒(méi)有必要掉奄,浪費(fèi)內(nèi)存空間。另外凤薛,使用該模式返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性之間沒(méi)有任何關(guān)系姓建。因此,使用instanceof運(yùn)算符和prototype屬性都沒(méi)有意義枉侧。所以引瀑,該模式要盡量避免使用。這種模式有點(diǎn)類似于你借別人的工廠生產(chǎn)的產(chǎn)品榨馁,無(wú)法貼上他們品牌的標(biāo)識(shí)憨栽。你借用的是工廠,但不是品牌翼虫。
function Doll(name,age){
// 這里也可以是new Array或者其他構(gòu)造函數(shù)屑柔,用于寄生
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var doll1 = new Doll("Nicholas",29);
var doll2 = new Doll("Greg",25);
//具有相同作用的sayName()方法在doll1和doll2這兩個(gè)實(shí)例中卻占用了不同的內(nèi)存空間
console.log(doll1.sayName === doll2.sayName); // false
console.log(doll1 instanceof Doll); // false
console.log(doll1.__proto__ === Doll.prototype); // false
3.2.3 穩(wěn)妥構(gòu)造函數(shù)模式
所謂穩(wěn)妥對(duì)象指沒(méi)有公共屬性,而且其方法也不引用this的對(duì)象珍剑。穩(wěn)妥對(duì)象最適合在一些安全環(huán)境中(這些環(huán)境會(huì)禁止使用this和new)或者在防止數(shù)據(jù)被其他應(yīng)用程序改動(dòng)時(shí)使用掸宛。
穩(wěn)妥構(gòu)造函數(shù)與寄生構(gòu)造函數(shù)模式相似,但有兩點(diǎn)不同:一是新創(chuàng)建對(duì)象的實(shí)例方法不引用this招拙;二是不使用new操作符調(diào)用構(gòu)造函數(shù)唧瘾。
function Doll(name,age){
//創(chuàng)建要返回的對(duì)象
var o = new Object();
//可以在這里定義私有變量和函數(shù)
//添加方法
o.sayName = function(){
console.log(name);
};
//返回對(duì)象
return o;
}
//在穩(wěn)妥模式創(chuàng)建的對(duì)象中措译,除了使用sayName()方法之外,沒(méi)有其他方法訪問(wèn)name的值
var doll = Person("Nicholas",29);
doll.sayName();//"Nicholas"
與寄生構(gòu)造函數(shù)模式相似饰序,使用穩(wěn)妥構(gòu)造函數(shù)模式創(chuàng)建的對(duì)象與構(gòu)造函數(shù)之間也沒(méi)有什么關(guān)系领虹,因此instanceof操作符對(duì)這種對(duì)象也沒(méi)有什么意義。好像是工廠針對(duì)客戶的定制生產(chǎn)一樣求豫。
3.3 原型模式
我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性塌衰,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象蝠嘉,而這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法最疆。如果按照字面意思來(lái)理解,那么prototype就是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的原型對(duì)象蚤告。使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法努酸。換句話說(shuō),不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息罩缴,而是可以將這些信息直接添加到原型對(duì)象中蚊逢。就好像做了一個(gè)模具,直接把信息刻在模具箫章,就可以利用該模具實(shí)現(xiàn)復(fù)制量產(chǎn)了。
function Doll() {
}
Doll.prototype.name = "Nicholas";
Doll.prototype.age = 29;
Doll.prototype.job = "Software Engineer";
Doll.prototype.sayName = function () {
alert(this.name);
};
var doll1 = new Doll();
doll1.sayName(); //"Nicholas"
var doll2 = new Doll();
doll2.sayName(); //"Nicholas"
alert(doll1.sayName == doll2.sayName); //true
為原型一個(gè)個(gè)添加屬性和方法太慢反镜会,我們可以考慮使用字面量來(lái)創(chuàng)建原型對(duì)象檬寂。
function Doll(){};
Doll.prototype = {
name: "Nicholas",
age: 29,
sayName : function(){
console.log(this.name);
}
};
var doll1 = new Doll();
doll1.sayName();//"Nicholas"
console.log(doll1.constructor === Doll);//false
console.log(doll1.constructor === Object);//true
但是,經(jīng)過(guò)對(duì)象字面量的改寫(xiě)后戳表,constructor不再指向Doll了桶至。因?yàn)榇朔椒ㄍ耆貙?xiě)了默認(rèn)的prototype對(duì)象,使得Doll.prototype的自有屬性constructor屬性不存在匾旭,只有從原型鏈中找到Object.prototype中的constructor屬性镣屹。
所以,可以顯式地設(shè)置原型對(duì)象的constructor屬性价涝。
function Doll(){};
Doll.prototype = {
constructor:Doll,
name: "Nicholas",
age: 29,
sayName : function(){
console.log(this.name);
}
};
var doll1 = new Doll();
doll1.sayName();//"Nicholas"
console.log(doll1.constructor === Doll);//true
console.log(doll1.constructor === Object);//false
由于默認(rèn)情況下女蜈,原生的constructor屬性是不可枚舉的,更妥善的解決方法是使用Object.defineProperty()方法色瘩,改變其屬性描述符中的枚舉性enumerable伪窖。
function Doll(){};
Doll.prototype = {
name: "Nicholas",
age: 29,
sayName : function(){
console.log(this.name);
}
};
Object.defineProperty(Doll.prototype,'constructor',{
enumerable: false,
value: Doll
});
var doll1 = new Doll();
doll1.sayName();//"Nicholas"
console.log(doll1.constructor === Doll);//true
console.log(doll1.constructor === Object);//false
原型模式問(wèn)題在于引用類型值屬性會(huì)被所有的實(shí)例對(duì)象共享并修改,這也是很少有人單獨(dú)使用原型模式的原因居兆。
function Doll(){};
Doll.prototype = {
constructor:Doll,
name: "Nicholas",
age: 29,
colors: ['blue', 'green'],
sayName : function(){
console.log(this.name);
}
};
var doll1 = new Doll();
var doll2 = new Doll();
doll1.colors.push('red');
console.log(doll1.colors);//['blue', 'green','red']
console.log(doll2.colors);//['blue', 'green','red']
console.log(doll1.colors === doll2.colors);//true
3.4 組合模式(使用最廣泛)
組合使用構(gòu)造函數(shù)模式和原型模式是創(chuàng)建自定義類型的最常見(jiàn)方式覆山。構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性泥栖,這種組合模式還支持向構(gòu)造函數(shù)傳遞參數(shù)簇宽。實(shí)例對(duì)象都有自己的一份實(shí)例屬性的副本勋篓,同時(shí)又共享對(duì)方法的引用,最大限度地節(jié)省了內(nèi)存魏割。該模式是目前使用最廣泛生巡、認(rèn)同度最高的一種創(chuàng)建自定義對(duì)象的模式。
function Doll(name, age) {
this.name = name;
this.age = age;
this.colors = ['blue','green'];
}
Doll.prototype = {
constructor:Doll,
sayName : function(){
console.log(this.name);
}
};
var doll1 = new Doll("Nicholas", 29);
var doll2 = new Doll("Greg", 27);
doll1.colors.push('red');
console.log(doll1.colors);//['blue', 'green','red']
console.log(doll2.colors);//['blue', 'green']
console.log(doll1.colors === doll2.colors);//false
console.log(doll1.sayName === doll2.sayName);//true
3.5 動(dòng)態(tài)原型模式
動(dòng)態(tài)原型模式將組合模式中分開(kāi)使用的構(gòu)造函數(shù)和原型對(duì)象都封裝到了構(gòu)造函數(shù)中见妒,然后通過(guò)檢查方法是否被創(chuàng)建孤荣,來(lái)決定是否初始化原型對(duì)象。
function Doll(name, age) {
//屬性
this.name = name;
this.age = age;
this.colors = ['blue','green'];
}
//方法
if(typeof this.sayName != "function"){
Doll.prototype.sayName = function(){
console.log(this.name);
};
Doll.prototype.sayColors = function(){
console.log(this.colors);
};
}
};
var doll1 = new Doll("Nicholas", 29);
doll1.sayName();//"Nicholas"
這里须揣,只有在sayName和sayColors方法不存在的情況下盐股,才會(huì)將它們添加到原型中。這段代碼只會(huì)在初次調(diào)用構(gòu)造函數(shù)時(shí)才執(zhí)行耻卡。此后疯汁,原型已經(jīng)初始化,不需要再做修改卵酪。另外幌蚊,這里對(duì)原型所做的修改,能夠立即在所有實(shí)例中得到反映溃卡。因此溢豆,除了不能使用對(duì)象字面量重寫(xiě)原型外,這種方法可謂相當(dāng)完美瘸羡。
注意漩仙,使用動(dòng)態(tài)原型模式時(shí):
- 如果原型對(duì)象中包含多個(gè)語(yǔ)句,只需要檢測(cè)其中一個(gè)語(yǔ)句即可犹赖。
- 不能使用對(duì)象字面量重寫(xiě)原型队他。因?yàn)樵谝呀?jīng)創(chuàng)建了實(shí)例的情況下重寫(xiě)原型,就會(huì)切斷現(xiàn)有實(shí)例與新原型的聯(lián)系峻村。
參考
javascript面向?qū)ο笙盗械诙獎(jiǎng)?chuàng)建對(duì)象的5種模式
深入理解javascript對(duì)象系列第一篇——初識(shí)對(duì)象
JavaScript構(gòu)造函數(shù)及原型對(duì)象
BOOK-《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》第6章