大家好挂据,我是IT修真院深圳分院第01期學(xué)員娃弓,一枚正直純潔善良的web程序員雾狈。
今天給大家分享一下,修真院官網(wǎng)JS(職業(yè))任務(wù)4母怜,深度思考中的知識(shí)點(diǎn)——JS面向?qū)ο缶幊?/p>
1.介紹
“面向?qū)ο缶幊獭保∣bject-Oriented Programming余耽,縮寫為OOP)是目前主流的編程范式。它的核心思想是將真實(shí)世界中各種復(fù)雜的關(guān)系苹熏,抽象為一個(gè)個(gè)對(duì)象碟贾,然后由對(duì)象之間的分工與合作,完成對(duì)真實(shí)世界的模擬轨域。
面向?qū)ο蟮恼Z言有一個(gè)標(biāo)志袱耽,就是類的概念,通過類可以創(chuàng)建任意多個(gè)具有相同屬性和方法的對(duì)象干发。ECMAScript中沒有類的概念扛邑,它的對(duì)象與基于類的語言中的對(duì)象有所不同。
2.涉及
2.1對(duì)象
ECMA-262 把對(duì)象定義為:無序?qū)傩缘募项砣唬鋵傩钥梢园局怠?duì)象或者函數(shù)恶座。嚴(yán)格來講搀暑,這就相當(dāng)于說對(duì)象是一組沒有特定順序的值。對(duì)象的每個(gè)屬性或方法都有一個(gè)名字跨琳,而每個(gè)名字都映射到一個(gè)值自点。正因?yàn)檫@樣(以及其他將要討論的原因).我們可以把 ECMAScript 的對(duì)象想象成散列表:無非就是一組名值對(duì),其中值可以是數(shù)據(jù)或函數(shù)脉让。
2.1.1Object構(gòu)造對(duì)象
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer ";
person.sayName = function() {
alert (this.name) ;
};
2.1.2對(duì)象字面量創(chuàng)建對(duì)象
var person = {
name: "Nicholas",
age : 29 ,
job: "Software Engineer",
sayName: function () {
alert(this.name) ;
}
};
2.2對(duì)象屬性類型
ECMA-262第5版定義了JS對(duì)象屬性的特征(用于JS引擎桂敛,外部無法直接訪問)功炮。ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。
2.2.1數(shù)據(jù)屬性
數(shù)據(jù)屬性指包含一個(gè)數(shù)據(jù)值的位置术唬,可在該位置讀取或?qū)懭胫敌椒?個(gè)供述其行為的特性:
[[configurable]]:表示能否通過 delete 刪除屬性從而重新定義屬性.能否修改屬性的特性,或者能否把屬性修改為訪問器屬性粗仓。默認(rèn)為true;
[[Enumerable]]:表示能否通過 for-in 循環(huán)返回屬性嫁怀。默認(rèn)為true;
[[Writable]]:表示能否修改屬性的值。默認(rèn)true;
[[Value]]:包含該屬性的數(shù)據(jù)值借浊。讀取/寫入都是該值塘淑。默認(rèn)為undefined;
如上面實(shí)例對(duì)象person中定義了name屬性,其值為’Nicholas’,對(duì)該值的修改都反映在這個(gè)位置蚂斤,要修改對(duì)象屬性的默認(rèn)特征(默認(rèn)都為true)存捺,必須使用用Object.defineProperty()方法,它接收三個(gè)參數(shù):屬性所在對(duì)象曙蒸,屬性名和一個(gè)描述符對(duì)象(必須是:configurable捌治、enumberable、writable和value逸爵,可設(shè)置一個(gè)或多個(gè)值)具滴。
var person = {};
Object.defineProperty(person, 'name', {
configurable: false,
writable: false,
value: 'Nicholas'
});
alert(person.name);//"Nicholas"
delete person.name;
person.name = 'aaa';
alert(person.name);//"Nicholas"
以上,delete及重置person.name的值都沒有生效师倔,這就是因?yàn)閏onfigurable: false和writable: false构韵;值得注意的是一旦將configurable設(shè)置為false,則無法再使用defineProperty將其修改為true(執(zhí)行會(huì)報(bào)錯(cuò):can't redefine non-configurable property);
2.2.2訪問器屬性
訪問器屬性不包含數(shù)據(jù)值趋艘。它包含一對(duì) getter 和 setter 函數(shù)(這兩個(gè)函數(shù)都不是必需的)疲恢。讀取訪問器屬性時(shí),調(diào)用 getter 函數(shù)瓷胧,返回有效的值显拳;寫入訪問器屬性時(shí),調(diào)用 setter 函數(shù)并傳入新值并設(shè)置搓萧。該屬性有以下4個(gè)特征:
[[Configurable]]:是否可通過delete刪除屬性從而重新定義屬性杂数,能否修改屬性的特性,或者能否把屬性修改為數(shù)據(jù)屬性瘸洛,默認(rèn)值為true揍移。
[[Enumerable]]:是否可通過for-in循環(huán)屬性;
[[Get]]:讀取屬性時(shí)調(diào)用反肋,默認(rèn):undefined;
[[Set]]:寫入屬性時(shí)調(diào)用那伐,默認(rèn):undefined;
訪問器屬性不能直接定義,必須使用defineProperty()來定義.如:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, 'year', {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year=newValue;
this.edition += newValue-2004;
}
}
});
book.year=2005;
alert(book.edition);//2
不一定非要同時(shí)指定 getter 和 setter,只指定 getter 意味著屬性是不能寫罕邀,嘗試寫入屬性會(huì)被忽略畅形。沒有指定getter函數(shù)的屬性也不能讀,會(huì)返回undefined。
此外诉探,ECMA-262(5)還提供了一個(gè)Object.defineProperties()方法日熬,可以用來一次性定義多個(gè)屬性的特性:
var book = {};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year=newValue;
this.edition += newValue-2004;
}}}
});
使用ECMAScript 5的Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符阵具。這個(gè)方法接收兩個(gè)參數(shù): 屬性所在的對(duì)象和要讀取其描述符的屬性名稱碍遍。返回值是一個(gè)對(duì)象,如果是訪問器屬性阳液,這個(gè)對(duì)象的屬性有configurable怕敬、enumerable、get和set; 如果是數(shù)據(jù)屬性帘皿,這個(gè)對(duì)象的屬性有configurable东跪、enumerable、writable和value鹰溜。
var descriptor = Object.getOwnPropertyDescriptor(book 虽填,"_year" ) ;
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //undefined
var descriptor = Object.getOwnPropertyDescriptor(book. "year");
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //function
2.3 創(chuàng)建對(duì)象
使用Object構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢詣?chuàng)建對(duì)象,缺點(diǎn)是創(chuàng)建多個(gè)對(duì)象時(shí)曹动,會(huì)產(chǎn)生大量的重復(fù)代碼斋日。因此使用用工廠模式的變體來解決問題。
2.3.1工廠模式:用函數(shù)來封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.getName = function () {
return this.name;
}
return o;//使用return返回生成的對(duì)象實(shí)例
}
var person = createPerson('Nicholas',29,'Software Engineer');
var person = createPerson('Greg',27,'Doctor');
創(chuàng)建對(duì)象交給一個(gè)工廠方法來實(shí)現(xiàn)墓陈,可以傳遞參數(shù)恶守。缺點(diǎn)是無法識(shí)別對(duì)象類型,因?yàn)閯?chuàng)建對(duì)象都是使用Object的原生構(gòu)造函數(shù)來完成的贡必。
2.3.2構(gòu)造函數(shù)模式:創(chuàng)建特定類型的對(duì)象
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.getName = function () {
return this.name;
}
}
var person1 = new Person('Nicholas',29,'Software Engineer');
var person2 = new Person('Greg',27,'Doctor');
使用自定義的構(gòu)造函數(shù)來創(chuàng)建對(duì)象兔港,它與工廠方法區(qū)別在于:
1.沒有顯式地創(chuàng)建對(duì)象
2.直接將屬性和方法賦值給this對(duì)象;
3.沒有return語句仔拟;
此外衫樊,要?jiǎng)?chuàng)建Person的實(shí)例,必須使用new關(guān)鍵字利花,以Person函數(shù)為構(gòu)造函數(shù)科侈,傳遞參數(shù)完成對(duì)象創(chuàng)建;實(shí)際創(chuàng)建經(jīng)過以下4個(gè)過程:
1.創(chuàng)建一個(gè)對(duì)象
2.將函數(shù)的作用域賦給新對(duì)象(因此this指向這個(gè)新對(duì)象炒事,如:person1)
3.執(zhí)行構(gòu)造函數(shù)的代碼
4.返回該對(duì)象
上面person1與person2都是Person的實(shí)例兑徘,可以使用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ù)方式也存在缺點(diǎn),那就是在創(chuàng)建對(duì)象時(shí),特別針對(duì)對(duì)象的屬性指向函數(shù)時(shí)欲侮,會(huì)重復(fù)的創(chuàng)建函數(shù)實(shí)例崭闲,以上述代碼為基礎(chǔ),可以改寫為:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function ("alert(this.name)");//與聲明函數(shù)在邏輯上是等價(jià)的
}
alert(person1.sayName == person2.sayName); //false
上述代碼威蕉,創(chuàng)建多個(gè)實(shí)例時(shí)刁俭,會(huì)重復(fù)調(diào)用new Function(),創(chuàng)建多個(gè)函數(shù)實(shí)例韧涨,這些函數(shù)實(shí)例不在一個(gè)作用域中牍戚,造成內(nèi)存浪費(fèi)。
可以在函數(shù)中定義一個(gè)this.sayName = sayName的引用虑粥,而sayName函數(shù)在Person外定義如孝,這樣可以解決重復(fù)創(chuàng)建函數(shù)實(shí)例問題,但在效果上并沒有起到封裝的效果娩贷,如下所示:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
}
var person1 = new Person('Nicholas',29,'Software Engineer');
var person2 = new Person('Greg',27,'Doctor');
2.3.3原型模式
JS每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性第晰,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象彬祖,它是所有通過new操作符使用函數(shù)創(chuàng)建的實(shí)例的原型對(duì)象茁瘦。原型對(duì)象最大特點(diǎn)是,所有對(duì)象實(shí)例共享它所包含的屬性和方法储笑,也就是說甜熔,所有在原型對(duì)象中創(chuàng)建的屬性或方法都直接被所有對(duì)象實(shí)例共享。
function Person(){
}
Person.prototype.name = 'Nicholas'; //使用原型來添加屬性
Person.prototype.age = 29;
person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName(); //Nicholas
var person2 = new Person();
person2.sayName(); //Nicholas
alert(person1.sayName === person2.sayName); //true;
原型模式的缺點(diǎn)突倍,它省略了為構(gòu)造函數(shù)傳遞初始化參數(shù)腔稀,結(jié)果所有實(shí)例在默認(rèn)情況下都將取得相同的屬性值。最主要是當(dāng)對(duì)象的屬性是引用類型時(shí)赘方,它的值是不變的烧颖,總是引用同一個(gè)外部對(duì)象,所有實(shí)例對(duì)該對(duì)象的操作都會(huì)影響其它實(shí)例:
function Person() {
}
Person.prototype ={
name:'Nicholas',
lessons = ['Math','Physics'];
}
var person1 = new Person();
var person2 = new Person();
person1.lessons.push('Biology');
alert(person2.lessons);//Math,Physics,Biology窄陡,修改person1影響了person2
2.3.4組合構(gòu)造函數(shù)及原型模式
目前最為常用的定義類型方式炕淮,是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例的屬性跳夭,而原型模式用于定義方法和共享的屬性涂圆。結(jié)果,每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性的副本币叹,但同時(shí)又共享著對(duì)方方法的引用润歉,最大限度的節(jié)約內(nèi)存。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Shelby','Court'];
}
Person.prototype ={
constructor: Person,
this.sayName: function() {
alert(this.name);
}
}
var person1 = new Person('Nicholas',29,'Software Engineer');
var person2 = new Person('Greg',27,'Doctor');
person1.friends.push('Van');
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(parson1.friends === parson2.friends); //false
alert(parson1.sayName === parson2.sayName); //true
2.3.5動(dòng)態(tài)原型模式
將所有信息封裝在構(gòu)造函數(shù)中颈抚,而通過在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下)踩衩,又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)。換句話說,可以通過檢查某個(gè)應(yīng)該存在的方法是否有效驱富,來決定是否需要初始化原型锚赤。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != 'function'){
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
var person1 = new Person('Nicholas',29,'Software Engineer');
person1.sayName();
方法代碼:if語句在sayName()方法不存在的情況下,將它添加到原型中褐鸥,只在初次調(diào)用構(gòu)造函數(shù)時(shí)執(zhí)行线脚。對(duì)于采用這種模式創(chuàng)建的對(duì)象,可以使用instanceof操作符確定它的類型叫榕。
2.4 繼承
ECMAScript 無法實(shí)現(xiàn)接口繼承浑侥,只支持實(shí)現(xiàn)繼承。
2.4.1原型鏈
上一期講過
2.4.2借用構(gòu)造函數(shù)
使用apply()和call()方法在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)晰绎。
function SuperType() {
this.colors = ['red', 'blue','green'];
}
function SubType() (
// 繼承了SuperType
SuperType.call(this);
}
var instance1 = new SubType() ;
instance1.colors.push("black");
alert (instance1.colors); / /"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
2.4.3組合繼承
使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承寓落,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function () (
alert(this.name);
};
function SubType(name,age) (
//繼承屬性
SuperType.call(this,name);
this.age = age;
}
/ /繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge = function() (
alert(this.age);
};
var instance1 = new SubType('Nicholas',29);
instance1.colors.push('black');
alert(instance1.colors); // "red,blue,green,black"
instance1.sayName(); //'Nicholas';
instance1.sayAge(); //29
var instance2 = new SubType('Greg',27);
alert(instance2.colors); //'red, blue, green'
instance2.sayName(); //'Greg';
instance2.sayAge(); //27
讓兩個(gè)不同的 SubType 實(shí)例既分別擁有自己屬性————包括colors屬性寒匙,又可以使用相同的方法零如。
此外,還存在下列可供選擇的繼承模式锄弱。
1).原型式繼承. 可以在不必預(yù)先定義構(gòu)造函數(shù)的情況下實(shí)現(xiàn)繼承考蕾,其本質(zhì)是執(zhí)行對(duì)給定對(duì)象的淺復(fù)制。而復(fù)制得到的副本還可以得到進(jìn)一步改造会宪。
2).寄生式繼承. 與原型式繼承非常相似.也是基于某個(gè)對(duì)象或某些信息創(chuàng)建一個(gè)對(duì)象肖卧,然后增強(qiáng)對(duì)象,最后返回對(duì)象掸鹅。為了解決組合繼承模式由于多次調(diào)用超類型構(gòu)造函數(shù)而導(dǎo)致的低效率問題塞帐,可以將這個(gè)模式與組合繼承一起使用。
3).寄生組合式繼承. 集寄生式繼承和組合繼承的優(yōu)點(diǎn)與一身巍沙,是實(shí)現(xiàn)基于類型繼承的最有效方式葵姥。
3.常見問題
面向?qū)ο缶幊?br>
4.解決方案
以上
5.編碼實(shí)戰(zhàn)
6.擴(kuò)展思考
面向?qū)ο笈c面向過程的區(qū)別?
傳統(tǒng)的過程式編程(procedural programming)由一系列函數(shù)或一系列指令組成句携,使用時(shí)一步步調(diào)用榔幸;而面向?qū)ο缶幊痰某绦蛴梢幌盗袑?duì)象組成。
每一個(gè)對(duì)象都是功能中心矮嫉,具有明確分工削咆,可以完成接受信息、處理數(shù)據(jù)蠢笋、發(fā)出信息等任務(wù)拨齐。因此,面向?qū)ο缶幊叹哂徐`活性昨寞、代碼的可重用性瞻惋、模塊性等特點(diǎn)厦滤,容易維護(hù)和開發(fā),非常適合多人合作的大型應(yīng)用型軟件項(xiàng)目熟史。
7.參考文獻(xiàn)
《Javascript高級(jí)程序設(shè)計(jì)》chapter 6
8.更多討論
狀態(tài)機(jī)也是用對(duì)象實(shí)現(xiàn)的馁害。
鳴謝
感謝大家觀看
------------------------------------------------------------------------------------------------------------------------
今天的分享就到這里啦,歡迎大家點(diǎn)贊蹂匹、轉(zhuǎn)發(fā)、留言凹蜈、拍磚~
下期預(yù)告:cookies限寞,sessionStorage和localStorage的區(qū)別?不見不散~