對(duì)象:即是一系列屬性和方法的集合。
- 1.字面量方式
- 2.工廠模式
- 3.構(gòu)造函數(shù)
- 4.原型模式
- 5.組合使用構(gòu)造函數(shù)和原型模式
- 6.動(dòng)態(tài)原型模式
以上六種方式是javascript創(chuàng)建對(duì)象的方法,每種方法都有自己的特點(diǎn),下面是對(duì)每種方式的具體介紹。
1.字面量方式
var obj = {};
obj.name = "tom";
obj.age = 18;
obj.say = function(){
console.log("my name is "+this.name);
};
上面這種字面量創(chuàng)建對(duì)象的方式,是我們?cè)诰帉?code>Javascript代碼時(shí),經(jīng)常用來創(chuàng)建對(duì)象的方式。它和以下代碼是等價(jià)的。
var obj = new Object();
obj.name = "tom";
obj.age = 18;
obj.say = function(){
console.log("my name is "+this.name);
};
使用字面量方式創(chuàng)建對(duì)象,顯示很方便,當(dāng)我們需要時(shí),隨時(shí)創(chuàng)建即可。但是它們只適合創(chuàng)建少量的對(duì)象。當(dāng)我們需要?jiǎng)?chuàng)建很多對(duì)象時(shí),顯然這種方式是不實(shí)用的。
2.工廠模式
當(dāng)我們需要?jiǎng)?chuàng)建出多個(gè)對(duì)象的時(shí)候,通過字面量方式創(chuàng)建對(duì)象的方法顯然是不合理的。我們希望有這樣一個(gè)容器,當(dāng)我們需要?jiǎng)?chuàng)建對(duì)象時(shí),我們只需將數(shù)據(jù)扔進(jìn)去,返回給我們的就是一個(gè)個(gè)對(duì)象,雖然對(duì)象的屬性值不盡相同,但是他們都擁有共同的屬性。工廠模式就是為我們提供這個(gè)服務(wù)的。
function Factory(name,age){
var obj = {};
obj.name = name;
obj.age = age;
obj.say = function(){
console.log("my age is "+this.age+"old");
}
return obj;//創(chuàng)建的對(duì)象以返回值的方式返回給我們。
}
當(dāng)我們需要?jiǎng)?chuàng)建對(duì)象的時(shí)候,我們將對(duì)象的私有數(shù)據(jù)扔進(jìn)Factory
這個(gè)機(jī)器工廠即可
。
代碼示例:
var person1 = Factory("jack",18);
var person2 = Factory("mark",22);
工廠方式的問題:使用工廠模式能夠創(chuàng)建一個(gè)包含所有信息的對(duì)象,可以無數(shù)次的調(diào)用的這個(gè)函數(shù)。雖然其解決了創(chuàng)建多個(gè)相似對(duì)象的問題,但卻沒有解決對(duì)象識(shí)別的問題,因?yàn)閯?chuàng)建出來的對(duì)象始終是個(gè)Object實(shí)例(即如何得知一個(gè)對(duì)象的類型)。
console.log(person1.constructor);//object()
console.log(person1 instanceof Factory)//false
對(duì)象的
constructor
屬性,指向創(chuàng)建該對(duì)象的構(gòu)造函數(shù)。
instanceof是用來判斷一個(gè)對(duì)象是否為一個(gè)構(gòu)造函數(shù)的實(shí)例。
3.構(gòu)造函數(shù)
在上文中,我們可以通過 new Object()
來實(shí)例化對(duì)象,Object()
和 Array()
、Date()
都是Javascript為我們提供的原生構(gòu)造函數(shù),我們可以通過這些構(gòu)造函數(shù)來創(chuàng)建出不同類型的對(duì)象。
var arr = new Array();//創(chuàng)建一個(gè)數(shù)組對(duì)象
var date= new Date();//創(chuàng)建一個(gè)時(shí)間對(duì)象
除了JS為我們提供的原生構(gòu)造函數(shù),我們也可以自己創(chuàng)建一個(gè)構(gòu)造函數(shù)。
function Friend(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.introduce = function(){
console.log("my name is "+this.name+",my job is "+this.job);
};
};
var friend1 = new Friend("Bob",17,"student");
var friend2 = new Friend("Alens",25,"teacher");
console.log(friend1.introduce==friend2.introduce)//false
構(gòu)造函數(shù)的特點(diǎn)
- 1.函數(shù)名首字母大寫,從而區(qū)別與普通的函數(shù)。
- 2.在構(gòu)造函數(shù)中使用this關(guān)鍵字
- 3.創(chuàng)建對(duì)象的時(shí)候使用new關(guān)鍵字
當(dāng)我們通過new+構(gòu)造函數(shù)
實(shí)例化一個(gè)對(duì)象的時(shí)候,實(shí)際上代碼會(huì)執(zhí)行以下操作。
var friend1 = new Friend("Bob",17,"student");;
//1.首先會(huì)先創(chuàng)建一個(gè)實(shí)例化對(duì)象friend1
//2.然后將構(gòu)造函數(shù)Friend的作用域賦給該對(duì)象(this即指向該對(duì)象),也就相當(dāng)于為該對(duì)象添加以下屬性
friend1.name = "Bob";
friend1.age = 17;
firend1.job = "student";
//3.最后將該對(duì)象返回
通過構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象,一眼看上去,感覺和工廠模式創(chuàng)建對(duì)象并沒有什么區(qū)別,但是通過這種方式創(chuàng)建出來的對(duì)象,我們可以明確的判斷創(chuàng)建該對(duì)象的構(gòu)造函數(shù)。
console.log(friend1.constructor);//function Friend()
console.log(friend1 instanceof Friend)//true
通過構(gòu)造函數(shù)創(chuàng)建對(duì)象的方式,既解決了字面量
方式創(chuàng)建對(duì)象的局限性(無法適應(yīng)創(chuàng)建多個(gè)對(duì)象
),也解決了工廠模式
創(chuàng)建出來對(duì)象的不可識(shí)別類型的欠缺性。但是,當(dāng)我們?cè)谕ㄟ^構(gòu)造函數(shù)創(chuàng)建多個(gè)對(duì)象的時(shí)候,雖然他們的屬性是不同的,但是這些對(duì)象卻擁有共同的方法introduce
,這樣的話,創(chuàng)建多少個(gè)對(duì)象,就會(huì)有多少個(gè)方法。這樣的話,顯得有些多余,并且對(duì)計(jì)算機(jī)內(nèi)存也不利。
使用構(gòu)造函數(shù)的主要問題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。——《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》
我們可以將這些對(duì)象共有的方法,定義在構(gòu)造函數(shù)外面,有一個(gè)變量來引用該方法。這樣的話,創(chuàng)建出來的每一個(gè)對(duì)象,都可以享用該方法。
function Friend(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.introduce = introduce;
}
function introduce(){
console.log("my name is "+this.name+",my job is "+this.job)
}
var person1 = new Friend("Bob",17,"student");
var person2 = new Friend("Alens",25,"teacher");
console.log(person1.introduce === person2.introduce); //true
雖然這種做法,可以解決內(nèi)存消耗,實(shí)現(xiàn)多個(gè)對(duì)象共享一個(gè)方法,但是顯然這種做法是不利于整體代碼的閱讀性。并且,構(gòu)造函數(shù)也被硬生生的拆開了。
4.原型模式
原型模式,是Javascript中的一種設(shè)計(jì)模式
。指的是每一個(gè)構(gòu)造函數(shù),都有自己的一個(gè)原型對(duì)象prototype
,而通過該構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象,都有一個(gè)屬性__proto__
,該屬性指向創(chuàng)建該對(duì)象的構(gòu)造函數(shù)的原型對(duì)象。即__proto__
指向構(gòu)造函數(shù)的prototype
對(duì)象。正是這一連接賦予了JS對(duì)象屬性的動(dòng)態(tài)搜索特性:如果在對(duì)象本身找不到某個(gè)屬性,那么就會(huì)通過這個(gè)連接到其原型對(duì)象中去找。
示例代碼:
//先聲明一個(gè)無參數(shù)、無內(nèi)容的“空”構(gòu)造函數(shù)
function Person() {
}
//使用對(duì)象字面量語法重寫Person的原型對(duì)象
Person.prototype = {
name: 'jack',
age: '25',
job: 'front-end web developer',
sayName: function () {
console.log(this.name);
}
};
//因?yàn)樯厦媸褂脤?duì)象字面量的方式完全重寫了原型對(duì)象,
//導(dǎo)致初始原型對(duì)象(Person.prototype)與構(gòu)造函數(shù)(Person)之間的聯(lián)系(constructor)被切斷,
//因此需要手動(dòng)進(jìn)行連接
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person
});
//測(cè)試
var person1 = new Person();
var person2 = new Person();
person2.name = 'Faker';//將person2對(duì)象的名字改為自身的名字
console.log(person1.name); //'jack'
console.log(person2.name); //'Faker'
console.log(person1.sayName()); //'jack'
console.log(person2.sayName()); //'Faker'
console.log(person1.sayName === person2.sayName); //true
可以看到,通過原型模式,我們同樣可以輕松地創(chuàng)建對(duì)象,而且可以像“繼承”一般得到我們?cè)谠蛯?duì)象中定義的默認(rèn)屬性,在此基礎(chǔ)上,我們也可以對(duì)該對(duì)象隨意地添加或修改屬性及值。此外,通過上面最后一句測(cè)試代碼還可以看出,其函數(shù)實(shí)現(xiàn)了完美的引用共享,從這一點(diǎn)上來說,原型模式真正解決了構(gòu)造函數(shù)模式不能共享內(nèi)部方法引用的問題。
原型模式看起來不錯(cuò),不過它也不是沒有缺點(diǎn)。第一,它不像構(gòu)造函數(shù)模式那樣,初始化時(shí)即提供參數(shù),這使得所有新創(chuàng)建的實(shí)例在一開始時(shí)長(zhǎng)得一模一樣;第二,封裝性欠佳;第三,對(duì)于包含引用類型值的屬性,會(huì)導(dǎo)致不應(yīng)該出現(xiàn)的屬性共享。
對(duì)于第三個(gè)缺點(diǎn),用代碼更能說明問題:
function Person() {
}
Person.prototype = {
constructor: Person, //這樣恢復(fù)連接會(huì)導(dǎo)致該屬性的[[Enumerable]]特性變?yōu)閠rue。上面的Object.defineProperty()才是完美寫法。
name: 'Chuck',
age: '25',
job: 'Software Engineer',
friends: ['Frank', 'Lambert'],
sayName: function () {
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('Lily');
console.log(person1.friends); //["Frank", "Lambert", "Lily"]
console.log(person2.friends); //["Frank", "Lambert", "Lily"]
一般而言,我們都希望各個(gè)對(duì)象各有各的屬性和值,相互沒有影響。可像上面示例一樣,原型模式共享了不應(yīng)該共享的屬性,這絕對(duì)不會(huì)是我們想要的結(jié)果
5.組合使用構(gòu)造函數(shù)和原型模式
通過對(duì)上面幾種創(chuàng)建對(duì)象方法的分析,我們希望是否有一種方式,能夠在快速創(chuàng)建多個(gè)對(duì)象的同時(shí),讓這些對(duì)象既擁有自己的私有屬性,也擁有一些共有的方法。同時(shí),也不破壞構(gòu)造函數(shù)創(chuàng)建對(duì)象的純粹性。
代碼示例:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Frank', 'Lambert'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name)
}
};
var person1 = new Person('tom', 25, 'Software Engineer');
var person2 = new Person('cindy', 18, 'doctor');
person1.friends.push('Lily');
console.log(person1.friends); //["Frank", "Lambert", "Lily"]
console.log(person2.friends); //["Frank", "Lambert"]
console.log(person1.sayName === person2.sayName); //true
通過這種方式創(chuàng)建出來的對(duì)象,使得對(duì)象實(shí)例擁有自己可完全支配的全部屬性,同時(shí)還共享了方法引用以節(jié)省內(nèi)存開銷。
6.動(dòng)態(tài)原型模式
到上一步的“組合模式”為止,就功能和性能上而言可以說已經(jīng)達(dá)到我們的要求了,現(xiàn)在我們考慮是否可以對(duì)代碼進(jìn)一步優(yōu)化,畢竟“組合模式”有兩段代碼,起碼封裝性看起來不夠好。
我們把需要共享的函數(shù)引用通過原型封裝在構(gòu)造函數(shù)中,在調(diào)用構(gòu)造函數(shù)初始化對(duì)象實(shí)例的同時(shí)將該函數(shù)追加到原型對(duì)象中。當(dāng)然,為了避免重復(fù)定義,需要加一個(gè)if判斷。代碼如下:
function Person(name, age, job, friends){
this.name = name;
this.age = age;
this.job = job;
this.friends = friends;
if (typeof Person.prototype.sayName !== 'function') {
Person.prototype.sayName = function(){
return this.name;
}
}
}
var person1 = new Person('chuck', 25, 'Software Engineer', ['A','B','C']);
console.log(person1.sayName()); //'chuck'
以上六種創(chuàng)建對(duì)象的方式,是我們?cè)诰帉懘a時(shí),經(jīng)常用到的方法,具體要用哪中方法,還是要根據(jù)實(shí)際情況決定的。