今天來淺談下面向?qū)ο螅嫦驅(qū)ο蟮恼Z言有一個(gè)標(biāo)志,即擁有類的概念顶岸,抽象實(shí)例對象的公共屬性與方法朱浴,基于類可以創(chuàng)建任意多個(gè)實(shí)例對象生兆,一般具有封裝、繼承、多態(tài)的特性!但JS中對象與純面向?qū)ο笳Z言中的對象是不同的肆资,ECMA標(biāo)準(zhǔn)定義JS中對象:無序?qū)傩缘募希鋵傩钥梢园局翟钪ァο蠡蛘吆瘮?shù)郑原。可以簡單理解為JS的對象是一組無序的值夜涕,其中的屬性或方法都有一個(gè)名字犯犁,根據(jù)這個(gè)名字可以訪問相映射的值(值可以是基本值/對象/方法)。
一女器、理解對象:
第一種:基于Object對象
varperson =newObject();
person.name ='My Name';
person.age = 18;
person.getName =function(){
returnthis.name;
}
第二種:對象字面量方式(比較清楚的查找對象包含的屬性及方法)
varperson = {
name :'My name',
age : 18,
getName :function(){
returnthis.name;
}
}
JS的對象可以使用‘.’操作符動態(tài)的擴(kuò)展其屬性酸役,可以使用’delete’操作符或?qū)傩灾翟O(shè)置為’undefined’來刪除屬性。如下:
person.newAtt=’newAttr’;//添加屬性
alert(person.newAtt);//new Attr
delete person.age;
alert(person.age);//undefined(刪除屬性后值為undefined);
二晓避、對象屬性類型
ECMA-262第5版定義了JS對象屬性中特征(用于JS引擎簇捍,外部無法直接訪問)。ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性
1俏拱、數(shù)據(jù)屬性:
數(shù)據(jù)屬性指包含一個(gè)數(shù)據(jù)值的位置暑塑,可在該位置讀取或?qū)懭胫担搶傩杂?個(gè)供述其行為的特性:
[[configurable]]:表示能否使用delete操作符刪除從而重新定義锅必,或能否修改為訪問器屬性事格。默認(rèn)為true;
[[Enumberable]]:表示是否可通過for-in循環(huán)返回屬性。默認(rèn)true;
[[Writable]]:表示是否可修改屬性的值搞隐。默認(rèn)true;
[[Value]]:包含該屬性的數(shù)據(jù)值驹愚。讀取/寫入都是該值。默認(rèn)為undefined;如上面實(shí)例對象person中定義了name屬性劣纲,其值為’My name’,對該值的修改都反正在這個(gè)位置
要修改對象屬性的默認(rèn)特征(默認(rèn)都為true)逢捺,可調(diào)用Object.defineProperty()方法,它接收三個(gè)參數(shù):屬性所在對象癞季,屬性名和一個(gè)描述符對象(必須是:configurable劫瞳、enumberable、writable和value绷柒,可設(shè)置一個(gè)或多個(gè)值)志于。
如下:(瀏覽器支持:IE9+、Firefox 4+废睦、Chrome伺绽、Safari5+)
varperson = {};
Object.defineProperty(person,'name', {
configurable:false,
writable:false,
value:'Jack'
});
alert(person.name);//Jack
delete person.name;
person.name ='lily';
alert(person.name);//Jack
可以看出,delete及重置person.name的值都沒有生效嗜湃,這就是因?yàn)檎{(diào)用defineProperty函數(shù)修改了對象屬性的特征奈应;值得注意的是一旦將configurable設(shè)置為false,則無法再使用defineProperty將其修改為true(執(zhí)行會報(bào)錯(cuò):can't redefine non-configurable property);
2购披、訪問器屬性:
它主要包括一對getter和setter函數(shù)钥组,在讀取訪問器屬性時(shí),會調(diào)用getter返回有效值今瀑;寫入訪問器屬性時(shí)程梦,調(diào)用setter,寫入新值橘荠;該屬性有以下4個(gè)特征:
[[Configurable]]:是否可通過delete操作符刪除重新定義屬性屿附;
[[Numberable]]:是否可通過for-in循環(huán)查找該屬性;
[[Get]]:讀取屬性時(shí)調(diào)用哥童,默認(rèn):undefined;
[[Set]]:寫入屬性時(shí)調(diào)用挺份,默認(rèn):undefined;
訪問器屬性不能直接定義,必須使用defineProperty()來定義贮懈,如下:
var person = {
_age: 18
};
Object.defineProperty(person,'isAdult', {
get:function() {
if(this._age >= 18) {
returntrue;
}else{
returnfalse;
}
}
});
alert(person.isAdult?'成年':'未成年');//成年
從上面可知匀泊,定義訪問器屬性時(shí)getter與setter函數(shù)不是必須的,并且优训,在定義getter與setter時(shí)不能指定屬性的configurable及writable特性;
此外各聘,ECMA-262(5)還提供了一個(gè)Object.defineProperties()方法揣非,可以用來一次性定義多個(gè)屬性的特性:
var person = {};
Object.defineProperties(person,{
_age:{
value:19
},
isAdult:{
get:function() {
if(this._age >= 18) {
returntrue;
}else{
returnfalse;
}
}
}
});
alert(person.isAdult?'成年':'未成年');//成年
上述代碼使用Object.defineProperties()方法同時(shí)定義了_age及isAudlt兩個(gè)屬性的特性
此外,使用Object.getOwnPropertyDescriptor()方法可以取得給定屬性的特性:
vardescriptor = Object.getOwnPropertyDescriptor(person,'_age');
alert(descriptor.value);//19
對于數(shù)據(jù)屬性躲因,可以取得:configurable,enumberable,writable和value早敬;
對于訪問器屬性,可以取得:configurable,enumberable,get和set
三大脉、創(chuàng)建對象
使用Object構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢詣?chuàng)建對象搞监,但缺點(diǎn)是創(chuàng)建多個(gè)對象時(shí),會產(chǎn)生大量的重復(fù)代碼镰矿,因此下面介紹可解決這個(gè)問題的創(chuàng)建對象的方法
1琐驴、工廠模式
functioncreatePerson(name, age, job) {
varo =newObject();
o.name = name;
o.age = age;
o.job = job;
o.getName =function() {
returnthis.name;
}
returno;//使用return返回生成的對象實(shí)例
}
varperson = createPerson('Jack', 19,'SoftWare Engineer');
創(chuàng)建對象交給一個(gè)工廠方法來實(shí)現(xiàn),可以傳遞參數(shù)秤标,但主要缺點(diǎn)是無法識別對象類型棍矛,因?yàn)閯?chuàng)建對象都是使用Object的原生構(gòu)造函數(shù)來完成的。
2抛杨、構(gòu)造函數(shù)模式
functionPerson(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.getName =function() {
returnthis.name;
}
}
varperson1 =newPerson('Jack', 19,'SoftWare Engineer');
varperson2 =newPerson('Liye', 23,'Mechanical Engineer');
使用自定義的構(gòu)造函數(shù)(與普通函數(shù)一樣够委,只是用它來創(chuàng)建對象),定義對象類型(如:Person)的屬性和方法怖现。它與工廠方法區(qū)別在于:
沒有顯式地創(chuàng)建對象
直接將屬性和方法賦值給this對象茁帽;
沒有return語句;
此外屈嗤,要?jiǎng)?chuàng)建Person的實(shí)例潘拨,必須使用new關(guān)鍵字,以Person函數(shù)為構(gòu)造函數(shù)饶号,傳遞參數(shù)完成對象創(chuàng)建铁追;實(shí)際創(chuàng)建經(jīng)過以下4個(gè)過程:
創(chuàng)建一個(gè)對象
將函數(shù)的作用域賦給新對象(因此this指向這個(gè)新對象,如:person1)
執(zhí)行構(gòu)造函數(shù)的代碼
返回該對象
上述由Person構(gòu)造函數(shù)生成的兩個(gè)對象person1與person2都是Person的實(shí)例茫船,因此可以使用instanceof判斷琅束,并且因?yàn)樗袑ο蠖祭^承Object,因此person1 instanceof Object也返回真:
alert(person1 instanceof Person);//true;
alert(person2 instanceof Person);//true;
alert(person1 instanceof Object);//true;
alert(person1.constructor === person2.constructor);//ture;
雖然構(gòu)造函數(shù)方式比較不錯(cuò)算谈,但也存在缺點(diǎn)涩禀,那就是在創(chuàng)建對象時(shí),特別針對對象的屬性指向函數(shù)時(shí)然眼,會重復(fù)的創(chuàng)建函數(shù)實(shí)例艾船,以上述代碼為基礎(chǔ),可以改寫為:
functionPerson(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.getName =newFunction () {//改寫后效果與原代碼相同,不過是為了方便理解
returnthis.name;
}
}
上述代碼屿岂,創(chuàng)建多個(gè)實(shí)例時(shí)践宴,會重復(fù)調(diào)用new Function();創(chuàng)建多個(gè)函數(shù)實(shí)例,這些函數(shù)實(shí)例還不是一個(gè)作用域中爷怀,當(dāng)然這一般不會有錯(cuò)阻肩,但這會造成內(nèi)存浪費(fèi)。當(dāng)然霉撵,可以在函數(shù)中定義一個(gè)getName = getName的引用,而getName函數(shù)在Person外定義洪囤,這樣可以解決重復(fù)創(chuàng)建函數(shù)實(shí)例問題徒坡,但在效果上并沒有起到封裝的效果,如下所示:
functionPerson(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.getName = getName;
}
functiongetName() {//到處是代碼瘤缩,看著亂@辍!
returnthis.name;
}
3剥啤、原型模式
JS每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性锦溪,這個(gè)屬性是一個(gè)指針,指向一個(gè)對象府怯,它是所有通過new操作符使用函數(shù)創(chuàng)建的實(shí)例的原型對象刻诊。原型對象最大特點(diǎn)是,所有對象實(shí)例共享它所包含的屬性和方法牺丙,也就是說则涯,所有在原型對象中創(chuàng)建的屬性或方法都直接被所有對象實(shí)例共享。
functionPerson(){
}
Person.prototype.name ='Jack';//使用原型來添加屬性
Person.prototype.age = 29;
Person.prototype.getName =function(){
returnthis.name;
}
varperson1 =newPerson();
alert(person1.getName());//Jack
varperson2 =newPerson();
alert(person1.getName === person2.getName);//true;共享一個(gè)原型對象的方法
原型是指向原型對象的冲簿,這個(gè)原型對象與構(gòu)造函數(shù)沒有太大關(guān)系粟判,唯一的關(guān)系是函數(shù)的prototype是指向這個(gè)原型對象!而基于構(gòu)造函數(shù)創(chuàng)建的對象實(shí)例也包含一個(gè)內(nèi)部指針為:[[prototype]]指向原型對象峦剔。
實(shí)例屬性或方法的訪問過程是一次搜索過程:
首先從對象實(shí)例本身開始档礁,如果找到屬性就直接返回該屬性值;
如果實(shí)例本身不存在要查找屬性吝沫,就繼續(xù)搜索指針指向的原型對象呻澜,在其中查找給定名字的屬性,如果有就返回惨险;
基于以上分析易迹,原型模式創(chuàng)建的對象實(shí)例,其屬性是共享原型對象的平道;但也可以自己實(shí)例中再進(jìn)行定義睹欲,在查找時(shí),就不從原型對象獲取,而是根據(jù)搜索原則窘疮,得到本實(shí)例的返回袋哼;簡單來說,就是實(shí)例中屬性會屏蔽原型對象中的屬性闸衫;
原型與in操作符
一句話:無論原型中屬性涛贯,還是對象實(shí)例的屬性,都可以使用in操作符訪問到蔚出;要想判斷是否是實(shí)例本身的屬性可以使用object.hasOwnProperty(‘a(chǎn)ttr’)來判斷弟翘;
原生對象中原型
原生對象中原型與普通對象的原型一樣,可以添加/修改屬性或方法骄酗,如以下代碼為所有字符串對象添加去左右空白原型方法:
String.prototype.trim =function(){
returnthis.replace(/^\s+/,'').replace(/\s+$/,'');
}
varstr ='? word space? ';
alert('!'+str.trim()+'!');//!word space!
原型模式的缺點(diǎn)稀余,它省略了為構(gòu)造函數(shù)傳遞初始化參數(shù),這在一定程序帶來不便趋翻;另外睛琳,最主要是當(dāng)對象的屬性是引用類型時(shí),它的值是不變的踏烙,總是引用同一個(gè)外部對象师骗,所有實(shí)例對該對象的操作都會其它實(shí)例:
functionPerson() {
}
Person.prototype.name ='Jack';
Person.prototype.lessons = ['Math','Physics'];
varperson1 =newPerson();
person1.lessons.push('Biology');
varperson2 =newPerson();
alert(person2.lessons);//Math,Physics,Biology,person1修改影響了person2
4讨惩、組合構(gòu)造函數(shù)及原型模式
目前最為常用的定義類型方式辟癌,是組合構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例的屬性荐捻,而原型模式用于定義方法和共享的屬性愿待。結(jié)果,每個(gè)實(shí)例都會有自己的一份實(shí)例屬性的副本靴患,但同時(shí)又共享著對方方法的引用仍侥,最大限度的節(jié)約內(nèi)存。此外鸳君,組合模式還支持向構(gòu)造函數(shù)傳遞參數(shù)农渊,可謂是集兩家之所長。
functionPerson(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.lessons = ['Math','Physics'];
}
Person.prototype = {
constructor: Person,//原型字面量方式會將對象的constructor變?yōu)镺bject或颊,此外強(qiáng)制指回Person
getName:function() {
returnthis.name;
}
}
varperson1 =newPerson('Jack', 19,'SoftWare Engneer');
person1.lessons.push('Biology');
varperson2 =newPerson('Lily', 39,'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定義方法
在所接觸的JS庫中砸紊,jQuery類型的封裝就是使用組合模式來實(shí)例的!4烟簟醉顽!
5、動態(tài)原型模式
組合模式中實(shí)例屬性與共享方法(由原型定義)是分離的平挑,這與純面向?qū)ο笳Z言不太一致游添;動態(tài)原型模式將所有構(gòu)造信息都封裝在構(gòu)造函數(shù)中系草,又保持了組合的優(yōu)點(diǎn)。其原理就是通過判斷構(gòu)造函數(shù)的原型中是否已經(jīng)定義了共享的方法或?qū)傩运衾裕绻麤]有則定義找都,否則不再執(zhí)行定義過程。該方式只原型上方法或?qū)傩灾欢x一次廊酣,且將所有構(gòu)造過程都封裝在構(gòu)造函數(shù)中能耻,對原型所做的修改能立即體現(xiàn)所有實(shí)例中:
functionPerson(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.lessons = ['Math','Physics'];
}
if (typeofthis.getName !='function') {//通過判斷實(shí)例封裝
Person.prototype = {
constructor: Person,//原型字面量方式會將對象的constructor變?yōu)镺bject,此外強(qiáng)制指回Person
getName:function () {
returnthis.name;
}
}
}
varperson1 =newPerson('Jack', 19,'SoftWare Engneer');
person1.lessons.push('Biology');
varperson2 =newPerson('Lily', 39,'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定義方法