面向?qū)ο笈c原型

ECMAScript有兩種開(kāi)發(fā)模式:1.函數(shù)式(過(guò)程化),2.面向?qū)ο?OOP)。面向?qū)ο蟮恼Z(yǔ)言有一個(gè)標(biāo)志,那就是類(lèi)的概念,而通過(guò)類(lèi)可以創(chuàng)建任意多個(gè)具有相同屬性和方法的對(duì)象。但是,ECMAScript沒(méi)有類(lèi)的概念,因此它的對(duì)象也與基于類(lèi)的語(yǔ)言中的對(duì)象有所不同。

面向過(guò)程就是你把代碼封裝成函數(shù)(procedure),然后依次去做一件事情;

面向?qū)ο?/b>就是你把要做的事情抽象成對(duì)象,告訴對(duì)象去做。


面向?qū)ο笕筇匦裕ǚ庋b,繼承,多態(tài))使得在做復(fù)雜的事情的時(shí)候效率和正確率得到保證



創(chuàng)建對(duì)象


1,用new關(guān)鍵字,創(chuàng)建一個(gè)對(duì)象,然后給這個(gè)對(duì)象新建屬性和方法。

var box = new Object();//創(chuàng)建一個(gè)Object對(duì)象

box.name = 'xiaoming';//創(chuàng)建一個(gè)name屬性并賦值

box.age = 18;//創(chuàng)建一個(gè)age屬性并賦值

box.run = function () {//創(chuàng)建一個(gè)run()方法并返回值

return this.name + this.age + '運(yùn)行中...';

};

alert(box.run());//輸出屬性和方法的值xiaoming18運(yùn)行中

上面創(chuàng)建了一個(gè)對(duì)象,并且創(chuàng)建屬性和方法,在run()方法里的this,就是代表box對(duì)象本身。這種是JavaScript創(chuàng)建對(duì)象最基本的方法,但有個(gè)缺點(diǎn),想創(chuàng)建一個(gè)類(lèi)似的對(duì)象,就會(huì)產(chǎn)生大量的代碼。

var box2 = box;//得到box的引用

box2.name = 'Jack';//直接改變了name屬性

alert(box2.run());//用box.run()發(fā)現(xiàn)name也改變了Jack18運(yùn)行中

alert(box.run());//兩者的值一樣,混淆了

var box2 = new Object();

box2.name = 'Jack';

box2.age = 200;

box2.run = function () {

return this.name + this.age + '運(yùn)行中...';

};

alert(box2.run());//這樣才避免和box混淆,從而保持獨(dú)立

2,字面量的方式創(chuàng)建對(duì)象? 即json方式

var obj2={

"name":"xiaoming",

"age":18,

"sing":function(){

return "我叫"+this.name+",今年"+this.age+"歲了";

} };

雖然json的方式也可以定義對(duì)象,但是它和new Object一樣存在了不能對(duì)象重用的缺陷,所以大家研究出了一種工廠方式來(lái)定義一個(gè)對(duì)象,下面我們來(lái)使用工廠方式來(lái)實(shí)現(xiàn)一個(gè)對(duì)象的定義。

3,利用工廠模式創(chuàng)建對(duì)象


functioncreatObj(name,sex){//傳參的方式,可以大批量的創(chuàng)建不同對(duì)象

varobj=newObject();

obj.name=name;

obj.sex=sex;

obj.sing=function(){

returnthis.name+"===>"+this.sex;

}

return obj; }

varp1=creatObj("小明","男");

alert(p1.sing());//小明的信息

varp2=creatObj("小花","女");

alert(p2.sing());//小花的信息


alert(typeofp1);//object 僅僅能識(shí)別p1只是一個(gè)對(duì)象 沒(méi)辦法進(jìn)行類(lèi)型的辨析

//工廠模式解決了對(duì)象無(wú)法重用的問(wèn)題,但是無(wú)法判斷對(duì)象的類(lèi)型

我們使用了工廠模式定義了對(duì)象,這樣就很好的解決了對(duì)象無(wú)法重用的問(wèn)題,但是此時(shí)又存在了另一個(gè)問(wèn)題,就是我們無(wú)法判斷得到的對(duì)象的類(lèi)型了,如typeof或者instanceof來(lái)判斷類(lèi)型,僅僅得到一個(gè)Object類(lèi)型。

4.構(gòu)造函數(shù)的方式創(chuàng)建對(duì)象

ECMAScript中可以采用構(gòu)造函數(shù)(構(gòu)造方法)可用來(lái)創(chuàng)建特定的對(duì)象。類(lèi)型于Object對(duì)象。

function Box(name, age) {//構(gòu)造函數(shù)模式

this.name = name;

this.age = age;

this.run = function () {

return this.name + this.age + '運(yùn)行中...';

}; }

ar box1 = new Box(xiaoming', 100);//new Box()即可? 類(lèi)的實(shí)例化,創(chuàng)建了對(duì)象實(shí)例

var box2 = new Box('Jack', 200);

alert(box1.run());

alert(box1 instanceof Box);//true很清晰的識(shí)別他從屬于Box

instanceof只能用來(lái)判斷對(duì)象和函數(shù),不能用來(lái)判斷字符串和數(shù)字等

使用構(gòu)造函數(shù)的方法,既解決了重復(fù)實(shí)例化的問(wèn)題,又解決了對(duì)象識(shí)別的問(wèn)題

構(gòu)造函數(shù)的方法有一些規(guī)范:

1.函數(shù)名和實(shí)例化構(gòu)造名相同且大寫(xiě),(PS:非強(qiáng)制,但這么寫(xiě)有助于區(qū)分構(gòu)造函數(shù)和普通函數(shù));

2.通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象,必須使用new運(yùn)算符。


既然通過(guò)構(gòu)造函數(shù)可以創(chuàng)建對(duì)象,那么這個(gè)對(duì)象是哪里來(lái)的,new Object()在什么地方執(zhí)行了?執(zhí)行的過(guò)程如下:

1.當(dāng)使用了構(gòu)造函數(shù),并且new構(gòu)造函數(shù)(),那么就后臺(tái)執(zhí)行了new Object();

2.將構(gòu)造函數(shù)的作用域給新對(duì)象,(即new Object()創(chuàng)建出的對(duì)象),而函數(shù)體內(nèi)的this就代表new Object()出來(lái)的對(duì)象。

3.執(zhí)行構(gòu)造函數(shù)內(nèi)的代碼;

4.返回新對(duì)象(后臺(tái)直接返回)。

此時(shí)我們發(fā)現(xiàn)基于構(gòu)造函數(shù)的定義對(duì)象的方式看似已經(jīng)很完美了,我們需要的問(wèn)題它都可以解決了,但是如果我們仔細(xì)的分析這段代碼的話,就會(huì)發(fā)現(xiàn)這樣的代碼是存在問(wèn)題的,為什么呢?

我們通過(guò)代碼分析得知:run方法在每個(gè)對(duì)象創(chuàng)建后都存在了一個(gè)方法拷貝(但是我們發(fā)現(xiàn)代碼在只有調(diào)用時(shí),run方法才會(huì)在堆中創(chuàng)建),這樣就增加了內(nèi)存的消耗了(閉包),如果在對(duì)象中有大量的方法時(shí),內(nèi)存的消耗就會(huì)高,這樣不行了。

function Person(name,sex){//函數(shù)名首字母記得大寫(xiě)

this.name=name;

this.sex=sex;

var a=12;//局部變量a

window.a=18;//window對(duì)象是a

this.say=say;//方法引用

}

function say(){

//全局的方法? 這樣每次調(diào)用的時(shí)候,用完了后,會(huì)直接銷(xiāo)毀該函數(shù),不會(huì)占用內(nèi)存

alert(this.name+"===>"+this.sex);}

//但是這用方法雖然解決了上述問(wèn)題,但又出現(xiàn)了新的問(wèn)題,就是破壞了對(duì)象的封裝性


var p1=new Person("夏明","男");//類(lèi)的實(shí)例化,創(chuàng)建了對(duì)象實(shí)例

var p2=new Person("小花","女");

p1.say();

alert(p1.name);//夏明

alert(p1.a);//undefined

alert(a);//18 ?window.a



構(gòu)造函數(shù)方式

創(chuàng)建原型對(duì)象


我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)對(duì)象,它的用途是包含可以由特定類(lèi)型的所有實(shí)例共享的屬性和方法。邏輯上可以這么理解:prototype通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象的原型對(duì)象。

使用原型的好處可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。也就是說(shuō),不必在構(gòu)造函數(shù)中定義對(duì)象信息,而是可以直接將這些信息添加到原型中。

functionPerson(){}//聲明構(gòu)造函數(shù)

Person.prototype.name="xiaoming";//在原型里添加屬性

Person.prototype.sex="男";

Person.prototype.sing=function(){//在原型里添加方法

alert(this.name);//this指向?qū)嵗龑?duì)象 原型對(duì)象

}

varp=newPerson();//p是Person的實(shí)例對(duì)象 指向原型對(duì)象

//p.sing();// ?xiaoming

//alert(p.name);//xiaoming

varp2=newPerson();

//如果給p2的屬性和方法重新賦值,會(huì)首先輸出重新賦的值,如果沒(méi)有重新賦值,會(huì)輸出原型里的值

p2.name="jack";

p2.sing=function(){

alert(this.name);//this指向p2實(shí)例對(duì)象

}

p2.sing();//jack

alert(p2.sex);//男 ??指向原型里的值



為了更進(jìn)一步了解構(gòu)造函數(shù)的聲明方式和原型模式的聲明方式,我們通過(guò)圖示來(lái)了解一下:


原型模式方式

在原型模式聲明中,多了兩個(gè)屬性,這兩個(gè)屬性都是創(chuàng)建對(duì)象時(shí)自動(dòng)生成的。__proto__屬性是實(shí)例指向原型對(duì)象的一個(gè)指針,它的作用就是指向構(gòu)造函數(shù)的原型屬性constructor。通過(guò)這兩個(gè)屬性,就可以訪問(wèn)到原型里的屬性和方法了。

PS:IE瀏覽器在腳本訪問(wèn)__proto__會(huì)不能識(shí)別,火狐和谷歌瀏覽器及其他某些瀏覽器均能識(shí)別。雖然可以輸出,但無(wú)法獲取內(nèi)部信息。

alert(box1.__proto__);//[object Object]

原型檢測(cè)方式:

1).isPrototypeOf()

判斷一個(gè)對(duì)象是否指向了該構(gòu)造函數(shù)的原型對(duì)象,可以使用isPrototypeOf()方法來(lái)測(cè)試。

alert(Box.prototype.isPrototypeOf(box));//只要實(shí)例化對(duì)象,即都會(huì)指向

原型模式的執(zhí)行流程:

1.先查找構(gòu)造函數(shù)實(shí)例里的屬性或方法,如果有,立刻返回;

2.如果構(gòu)造函數(shù)實(shí)例里沒(méi)有,則去它的原型對(duì)象里找,如果有,就返回;

雖然我們可以通過(guò)對(duì)象實(shí)例訪問(wèn)保存在原型中的值,但卻不能訪問(wèn)通過(guò)對(duì)象實(shí)例重寫(xiě)原型中的值。

var box1 = new Box();

alert(box1.name);//原型里的值

box1.name = 'Jack';

alert(box1. name);//Jack,就近原則,

var box2 = new Box();

alert(box2.name);//原型里的值,沒(méi)有被box1修改

如果想要box1也能在后面繼續(xù)訪問(wèn)到原型里的值,可以把構(gòu)造函數(shù)里的屬性刪除即可,具體如下:

delete box1.name;//刪除屬性delete只能刪除自己的,不能刪除原型里的屬性

alert(box1.name);//原型里的name值

2).hasOwnProperty()

如何判斷屬性是在構(gòu)造函數(shù)的實(shí)例里,還是在原型里?可以使用hasOwnProperty()函數(shù)來(lái)驗(yàn)證:

alert(box.hasOwnProperty('name'));//實(shí)例里有返回true,否則返回false


構(gòu)造函數(shù)實(shí)例屬性和原型屬性示意圖

3).in操作符

in操作符會(huì)在通過(guò)對(duì)象能夠訪問(wèn)給定屬性時(shí)返回true,無(wú)論該屬性存在于實(shí)例中還是原型中。

alert('name' in box);//true,存在實(shí)例中或原型中

我們可以通過(guò)hasOwnProperty()方法檢測(cè)屬性是否存在實(shí)例中,也可以通過(guò)in來(lái)判斷實(shí)例或原型中是否存在屬性。那么結(jié)合這兩種方法,可以判斷原型中是否存在屬性。

function isProperty(object, property) {//判斷原型中是否存在屬性

return !object.hasOwnProperty(property) && (property in object);

}

或者:

functionisProperty(obj,prop){

if(!obj.hasOwnProperty(prop)){

if(propinobj){

returntrue;

} }

returnfalse; }

var box = new Box();

alert(isProperty(box, 'name'))//true,如果原型有


3.原型重寫(xiě)覆蓋

為了讓屬性和方法更好的體現(xiàn)封裝的效果,并且減少不必要的輸入,原型的創(chuàng)建可以使用字面量的方式:

function Box() {};

Box.prototype = {//使用字面量的方式

name : 'xiaoming',

age : 100,

run : function () {

return this.name + this.age + '運(yùn)行中...';

}

};

使用構(gòu)造函數(shù)創(chuàng)建原型對(duì)象和使用字面量創(chuàng)建對(duì)象在使用上基本相同,但還是有一些區(qū)別,字面量創(chuàng)建的方式使用constructor屬性不會(huì)指向?qū)嵗鴷?huì)指向Object,構(gòu)造函數(shù)創(chuàng)建的方式則相反。

var box = new Box();

alert(box instanceof Box);

alert(box instanceof Object);

alert(box.constructor == Box);//字面量方式,返回false,否則,true

alert(box.constructor == Object);//字面量方式,返回true,否則,false

如果想讓字面量方式的constructor指向?qū)嵗龑?duì)象,那么可以這么做:

Box.prototype = {

constructor : Box,//直接強(qiáng)制指向即可

};

PS:字面量方式為什么constructor會(huì)指向Object?因?yàn)锽ox.prototype={};這種寫(xiě)法其實(shí)就是創(chuàng)建了一個(gè)新對(duì)象。而每創(chuàng)建一個(gè)函數(shù),就會(huì)同時(shí)創(chuàng)建它prototype,這個(gè)對(duì)象也會(huì)自動(dòng)獲取constructor屬性。所以,新對(duì)象的constructor重寫(xiě)了Box原來(lái)的constructor,因此會(huì)指向新對(duì)象,那個(gè)新對(duì)象沒(méi)有指定構(gòu)造函數(shù),那么就默認(rèn)為Object。

原型的聲明是有先后順序的,所以,重寫(xiě)的原型會(huì)切斷之前的原型。

function Box() {};

Box.prototype = {//原型被重寫(xiě)了

constructor : Box,

name : 'Zeng',

age : 100,

run : function () {

return this.name + this.age + '運(yùn)行中...';

}

};

Box.prototype = {

age = 200

};

var box = new Box();//在這里聲明

alert(box.run());//box只是最初聲明的原型

原型對(duì)象不僅僅可以在自定義對(duì)象的情況下使用,而ECMAScript內(nèi)置的引用類(lèi)型都可以使用這種方式,并且內(nèi)置的引用類(lèi)型本身也使用了原型。

alert(Array.prototype.sort);//sort就是Array類(lèi)型的原型方法

alert(String.prototype.substring);//substring就是String類(lèi)型的原型方法

String.prototype.addstring = function () {//給String類(lèi)型添加一個(gè)方法

return this + ',被添加了!';//this代表調(diào)用的字符串

};

alert('Zeng'.addstring());//使用這個(gè)方法

PS:盡管給原生的內(nèi)置引用類(lèi)型添加方法使用起來(lái)特別方便,但我們不推薦使用這種方法。因?yàn)樗赡軙?huì)導(dǎo)致命名沖突,不利于代碼維護(hù)。

4.原型對(duì)象出現(xiàn)的問(wèn)題

原型模式創(chuàng)建對(duì)象也有自己的缺點(diǎn),它省略了構(gòu)造函數(shù)傳參初始化這一過(guò)程,帶來(lái)的缺點(diǎn)就是初始化的值都是一致的。而原型最大的缺點(diǎn)就是它最大的優(yōu)點(diǎn),那就是共享。

原型中所有屬性是被很多實(shí)例共享的,共享對(duì)于函數(shù)非常合適,對(duì)于包含基本值的屬性也還可以。但如果屬性包含引用類(lèi)型,就存在一定的問(wèn)題:

function Box() {};

Box.prototype = {

constructor : Box,

name : '小明',

age : 10,

family : ['父親', '母親', '妹妹'],//添加了一個(gè)數(shù)組屬性

run : function () {

return this.name + this.age + this.family;

}

};

var box1 = new Box();

box1.family.push('哥哥');//在實(shí)例中添加'哥哥'

alert(box1.run());

var box2 = new Box();

alert(box2.run());//共享帶來(lái)的麻煩,也有'哥哥'了

PS:數(shù)據(jù)共享的緣故,導(dǎo)致很多開(kāi)發(fā)者放棄使用原型,因?yàn)槊看螌?shí)例化出的數(shù)據(jù)需要保留自己的特性,而不能共享。

5.組合模式

為了解決構(gòu)造傳參和共享問(wèn)題,可以組合構(gòu)造函數(shù)+原型模式

屬性在構(gòu)造函數(shù)中定義,將方法在原型中定義。這種有效集合了兩者的優(yōu)點(diǎn),是目前最為常用的一種方式

function Box(name, age) {//不共享的使用構(gòu)造函數(shù)

this.name = name;

this.age = age;

this. family = ['父親', '母親', '妹妹'];

};

Box.prototype = {//共享的使用原型模式

constructor : Box,

run : function () {

return this.name + this.age + this.family;

}

};


PS:這種混合模式很好的解決了傳參和引用共享的大難題。是創(chuàng)建對(duì)象比較好的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容