ECMAScript有兩種開發(fā)模式:1.函數式(過程化)恋追,2.面向對象(OOP)凭迹。面向對象的語言有一個標志,那就是類的概念苦囱,而通過類可以創(chuàng)建任意多個具有相同屬性和方法的對象嗅绸。但是,ECMAScript沒有類的概念撕彤,因此它的對象也與基于類的語言中的對象有所不同鱼鸠。
面向過程就是你把代碼封裝成函數(procedure),然后依次去做一件事情羹铅;
面向對象就是你把要做的事情抽象成對象蚀狰,告訴對象去做。
面向對象三大特性(封裝职员,繼承麻蹋,多態(tài))使得在做復雜的事情的時候效率和正確率得到保證
創(chuàng)建對象
1,用new關鍵字焊切,創(chuàng)建一個對象扮授,然后給這個對象新建屬性和方法芳室。
var box = new Object();//創(chuàng)建一個Object對象
box.name = 'xiaoming';//創(chuàng)建一個name屬性并賦值
box.age = 18;//創(chuàng)建一個age屬性并賦值
box.run = function () {//創(chuàng)建一個run()方法并返回值
return this.name + this.age + '運行中...';
};
alert(box.run());//輸出屬性和方法的值xiaoming18運行中
上面創(chuàng)建了一個對象,并且創(chuàng)建屬性和方法刹勃,在run()方法里的this渤愁,就是代表box對象本身。這種是JavaScript創(chuàng)建對象最基本的方法深夯,但有個缺點,想創(chuàng)建一個類似的對象诺苹,就會產生大量的代碼咕晋。
var box2 = box;//得到box的引用
box2.name = 'Jack';//直接改變了name屬性
alert(box2.run());//用box.run()發(fā)現name也改變了Jack18運行中
alert(box.run());//兩者的值一樣,混淆了
var box2 = new Object();
box2.name = 'Jack';
box2.age = 200;
box2.run = function () {
return this.name + this.age + '運行中...';
};
alert(box2.run());//這樣才避免和box混淆收奔,從而保持獨立
2掌呜,字面量的方式創(chuàng)建對象? 即json方式
var obj2={
"name":"xiaoming",
"age":18,
"sing":function(){
return "我叫"+this.name+",今年"+this.age+"歲了";
} };
雖然json的方式也可以定義對象坪哄,但是它和new Object一樣存在了不能對象重用的缺陷质蕉,所以大家研究出了一種工廠方式來定義一個對象,下面我們來使用工廠方式來實現一個對象的定義翩肌。
3,利用工廠模式創(chuàng)建對象
functioncreatObj(name,sex){//傳參的方式模暗,可以大批量的創(chuàng)建不同對象
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 僅僅能識別p1只是一個對象 沒辦法進行類型的辨析
//工廠模式解決了對象無法重用的問題,但是無法判斷對象的類型
我們使用了工廠模式定義了對象念祭,這樣就很好的解決了對象無法重用的問題兑宇,但是此時又存在了另一個問題,就是我們無法判斷得到的對象的類型了粱坤,如typeof或者instanceof來判斷類型隶糕,僅僅得到一個Object類型。
4.構造函數的方式創(chuàng)建對象
ECMAScript中可以采用構造函數(構造方法)可用來創(chuàng)建特定的對象站玄。類型于Object對象枚驻。
function Box(name, age) {//構造函數模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '運行中...';
}; }
ar box1 = new Box(xiaoming', 100);//new Box()即可? 類的實例化,創(chuàng)建了對象實例
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box);//true很清晰的識別他從屬于Box
instanceof只能用來判斷對象和函數株旷,不能用來判斷字符串和數字等
使用構造函數的方法再登,既解決了重復實例化的問題,又解決了對象識別的問題
構造函數的方法有一些規(guī)范:
1.函數名和實例化構造名相同且大寫灾常,(PS:非強制霎冯,但這么寫有助于區(qū)分構造函數和普通函數);
2.通過構造函數創(chuàng)建對象钞瀑,必須使用new運算符沈撞。
既然通過構造函數可以創(chuàng)建對象,那么這個對象是哪里來的雕什,new Object()在什么地方執(zhí)行了缠俺?執(zhí)行的過程如下:
1.當使用了構造函數显晶,并且new構造函數(),那么就后臺執(zhí)行了new Object()壹士;
2.將構造函數的作用域給新對象磷雇,(即new Object()創(chuàng)建出的對象),而函數體內的this就代表new Object()出來的對象躏救。
3.執(zhí)行構造函數內的代碼唯笙;
4.返回新對象(后臺直接返回)。
此時我們發(fā)現基于構造函數的定義對象的方式看似已經很完美了盒使,我們需要的問題它都可以解決了崩掘,但是如果我們仔細的分析這段代碼的話,就會發(fā)現這樣的代碼是存在問題的少办,為什么呢苞慢?
我們通過代碼分析得知:run方法在每個對象創(chuàng)建后都存在了一個方法拷貝(但是我們發(fā)現代碼在只有調用時,run方法才會在堆中創(chuàng)建)英妓,這樣就增加了內存的消耗了(閉包)挽放,如果在對象中有大量的方法時,內存的消耗就會高蔓纠,這樣不行了辑畦。
function Person(name,sex){//函數名首字母記得大寫
this.name=name;
this.sex=sex;
var a=12;//局部變量a
window.a=18;//window對象是a
this.say=say;//方法引用
}
function say(){
//全局的方法? 這樣每次調用的時候,用完了后贺纲,會直接銷毀該函數航闺,不會占用內存
alert(this.name+"===>"+this.sex);}
//但是這用方法雖然解決了上述問題,但又出現了新的問題猴誊,就是破壞了對象的封裝性
var p1=new Person("夏明","男");//類的實例化潦刃,創(chuàng)建了對象實例
var p2=new Person("小花","女");
p1.say();
alert(p1.name);//夏明
alert(p1.a);//undefined
alert(a);//18 ?window.a
創(chuàng)建原型對象
我們創(chuàng)建的每個函數都有一個prototype(原型)屬性,這個屬性是一個對象懈叹,它的用途是包含可以由特定類型的所有實例共享的屬性和方法乖杠。邏輯上可以這么理解:prototype通過調用構造函數而創(chuàng)建的那個對象的原型對象。
使用原型的好處可以讓所有對象實例共享它所包含的屬性和方法澄成。也就是說胧洒,不必在構造函數中定義對象信息,而是可以直接將這些信息添加到原型中墨状。
functionPerson(){}//聲明構造函數
Person.prototype.name="xiaoming";//在原型里添加屬性
Person.prototype.sex="男";
Person.prototype.sing=function(){//在原型里添加方法
alert(this.name);//this指向實例對象 原型對象
}
varp=newPerson();//p是Person的實例對象 指向原型對象
//p.sing();// ?xiaoming
//alert(p.name);//xiaoming
varp2=newPerson();
//如果給p2的屬性和方法重新賦值卫漫,會首先輸出重新賦的值,如果沒有重新賦值肾砂,會輸出原型里的值
p2.name="jack";
p2.sing=function(){
alert(this.name);//this指向p2實例對象
}
p2.sing();//jack
alert(p2.sex);//男 ??指向原型里的值
為了更進一步了解構造函數的聲明方式和原型模式的聲明方式列赎,我們通過圖示來了解一下:
在原型模式聲明中,多了兩個屬性镐确,這兩個屬性都是創(chuàng)建對象時自動生成的包吝。__proto__屬性是實例指向原型對象的一個指針饼煞,它的作用就是指向構造函數的原型屬性constructor。通過這兩個屬性诗越,就可以訪問到原型里的屬性和方法了砖瞧。
PS:IE瀏覽器在腳本訪問__proto__會不能識別,火狐和谷歌瀏覽器及其他某些瀏覽器均能識別嚷狞。雖然可以輸出块促,但無法獲取內部信息。
alert(box1.__proto__);//[object Object]
原型檢測方式:
1).isPrototypeOf()
判斷一個對象是否指向了該構造函數的原型對象床未,可以使用isPrototypeOf()方法來測試褂乍。
alert(Box.prototype.isPrototypeOf(box));//只要實例化對象,即都會指向
原型模式的執(zhí)行流程:
1.先查找構造函數實例里的屬性或方法即硼,如果有,立刻返回屡拨;
2.如果構造函數實例里沒有只酥,則去它的原型對象里找,如果有呀狼,就返回裂允;
雖然我們可以通過對象實例訪問保存在原型中的值,但卻不能訪問通過對象實例重寫原型中的值哥艇。
var box1 = new Box();
alert(box1.name);//原型里的值
box1.name = 'Jack';
alert(box1. name);//Jack绝编,就近原則,
var box2 = new Box();
alert(box2.name);//原型里的值貌踏,沒有被box1修改
如果想要box1也能在后面繼續(xù)訪問到原型里的值十饥,可以把構造函數里的屬性刪除即可,具體如下:
delete box1.name;//刪除屬性delete只能刪除自己的祖乳,不能刪除原型里的屬性
alert(box1.name);//原型里的name值
2).hasOwnProperty()
如何判斷屬性是在構造函數的實例里逗堵,還是在原型里?可以使用hasOwnProperty()函數來驗證:
alert(box.hasOwnProperty('name'));//實例里有返回true眷昆,否則返回false
3).in操作符
in操作符會在通過對象能夠訪問給定屬性時返回true蜒秤,無論該屬性存在于實例中還是原型中。
alert('name' in box);//true亚斋,存在實例中或原型中
我們可以通過hasOwnProperty()方法檢測屬性是否存在實例中作媚,也可以通過in來判斷實例或原型中是否存在屬性。那么結合這兩種方法帅刊,可以判斷原型中是否存在屬性纸泡。
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.原型重寫(覆蓋)
為了讓屬性和方法更好的體現封裝的效果厚掷,并且減少不必要的輸入弟灼,原型的創(chuàng)建可以使用字面量的方式:
function Box() {};
Box.prototype = {//使用字面量的方式
name : 'xiaoming',
age : 100,
run : function () {
return this.name + this.age + '運行中...';
}
};
使用構造函數創(chuàng)建原型對象和使用字面量創(chuàng)建對象在使用上基本相同级解,但還是有一些區(qū)別,字面量創(chuàng)建的方式使用constructor屬性不會指向實例田绑,而會指向Object勤哗,構造函數創(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指向實例對象涮帘,那么可以這么做:
Box.prototype = {
constructor : Box,//直接強制指向即可
};
PS:字面量方式為什么constructor會指向Object拼苍?因為Box.prototype={};這種寫法其實就是創(chuàng)建了一個新對象。而每創(chuàng)建一個函數调缨,就會同時創(chuàng)建它prototype疮鲫,這個對象也會自動獲取constructor屬性。所以弦叶,新對象的constructor重寫了Box原來的constructor俊犯,因此會指向新對象,那個新對象沒有指定構造函數伤哺,那么就默認為Object燕侠。
原型的聲明是有先后順序的,所以立莉,重寫的原型會切斷之前的原型绢彤。
function Box() {};
Box.prototype = {//原型被重寫了
constructor : Box,
name : 'Zeng',
age : 100,
run : function () {
return this.name + this.age + '運行中...';
}
};
Box.prototype = {
age = 200
};
var box = new Box();//在這里聲明
alert(box.run());//box只是最初聲明的原型
原型對象不僅僅可以在自定義對象的情況下使用,而ECMAScript內置的引用類型都可以使用這種方式蜓耻,并且內置的引用類型本身也使用了原型杖虾。
alert(Array.prototype.sort);//sort就是Array類型的原型方法
alert(String.prototype.substring);//substring就是String類型的原型方法
String.prototype.addstring = function () {//給String類型添加一個方法
return this + ',被添加了媒熊!';//this代表調用的字符串
};
alert('Zeng'.addstring());//使用這個方法
PS:盡管給原生的內置引用類型添加方法使用起來特別方便奇适,但我們不推薦使用這種方法。因為它可能會導致命名沖突芦鳍,不利于代碼維護嚷往。
4.原型對象出現的問題
原型模式創(chuàng)建對象也有自己的缺點,它省略了構造函數傳參初始化這一過程柠衅,帶來的缺點就是初始化的值都是一致的皮仁。而原型最大的缺點就是它最大的優(yōu)點,那就是共享。
原型中所有屬性是被很多實例共享的贷祈,共享對于函數非常合適趋急,對于包含基本值的屬性也還可以。但如果屬性包含引用類型势誊,就存在一定的問題:
function Box() {};
Box.prototype = {
constructor : Box,
name : '小明',
age : 10,
family : ['父親', '母親', '妹妹'],//添加了一個數組屬性
run : function () {
return this.name + this.age + this.family;
}
};
var box1 = new Box();
box1.family.push('哥哥');//在實例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run());//共享帶來的麻煩呜达,也有'哥哥'了
PS:數據共享的緣故,導致很多開發(fā)者放棄使用原型粟耻,因為每次實例化出的數據需要保留自己的特性查近,而不能共享。
5.組合模式
為了解決構造傳參和共享問題挤忙,可以組合構造函數+原型模式:
屬性在構造函數中定義霜威,將方法在原型中定義。這種有效集合了兩者的優(yōu)點册烈,是目前最為常用的一種方式
function Box(name, age) {//不共享的使用構造函數
this.name = name;
this.age = age;
this. family = ['父親', '母親', '妹妹'];
};
Box.prototype = {//共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};
PS:這種混合模式很好的解決了傳參和引用共享的大難題戈泼。是創(chuàng)建對象比較好的方法。