已經(jīng)忘了多久沒拿起javascirpt的書本了,無聊中拾起快被我遺忘在角落的書本<javascript高級(jí)程序設(shè)計(jì)>。
尼古拉斯竟然把這么奇葩的js面向?qū)ο笳f的這么好
從最原始的工廠模板說起
function createPerson(name,age){
?var o = new Object();
?o.name = name;
?o.age = age;
o.sayName = function(){
? console.log(this.name);
}
?return o;
}
這樣,but...等等,如何知道創(chuàng)建的對(duì)象是什么類型的?
構(gòu)造函數(shù)模式
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
}
var person1 = new Person("三毛",25);
簡(jiǎn)潔了不少,問題又來了:難道我每次new一個(gè)對(duì)象,就要重新創(chuàng)建個(gè)sayName函數(shù)嗎?為什么這么說?
函數(shù)也是對(duì)象,每次實(shí)例化Person時(shí),都會(huì)實(shí)例化一個(gè)sayName Function。也就是說:每個(gè)Person的示例,都有一個(gè)不同的sayName Function實(shí)例。何必如此?
理解原型模式
原型模式很好的解決了上述問題,這么說吧,每個(gè)function都有一個(gè)prototype屬性(我們叫原型),指向一個(gè)原型對(duì)象。
所以,我們可以把公共的方法屬性都加到它上面。
function Person(){
}
Person.prototype.name = "三毛";
Person.prototype.age = 25;
Person.prototype.sayName = function(){
?console.log(this.name);
}
把屬性方法加到了函數(shù)的prototype(實(shí)際上也是個(gè)對(duì)象)上,后續(xù)創(chuàng)建的實(shí)例對(duì)象都從這個(gè)原型對(duì)象上繼承了屬性和方法。
然而,它也不是完美的。原型對(duì)象的問題也是顯而易見的:由于所有的實(shí)例都是共享原型對(duì)象的方法屬性,那么意思就是說所有的實(shí)例共享了一份屬性方法!那創(chuàng)建的實(shí)例還有個(gè)毛用?
聯(lián)想到前面我們提過的構(gòu)造函數(shù)模式,我們可以把構(gòu)造函數(shù)+原型模式結(jié)合起來。
構(gòu)造函數(shù)負(fù)責(zé)自有的屬性,而原型對(duì)象負(fù)責(zé)公共的方法,一種新的模式應(yīng)運(yùn)而生:
組合模式
Person.prototype.sayName = function(){
console.log(this.name);
}
完美解決了構(gòu)造函數(shù)模式和原型模式的問題。
再談?wù)刯avascript繼承
在“理解原型模式”中,已經(jīng)談到了js的原型模式,而js的繼承就是依靠原型鏈來實(shí)現(xiàn)的。
js原型鏈
我們來簡(jiǎn)單回顧下前面講過的內(nèi)容:
1 每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象;
2 原型對(duì)象包含一個(gè)指向構(gòu)造函數(shù)的指針(證明自己是從哪來的);
3 構(gòu)造函數(shù)的實(shí)例(這么說可能有點(diǎn)不準(zhǔn)確)包含一個(gè)指向原型對(duì)象的指針。
有點(diǎn)暈,感覺有點(diǎn)像三角戀,看個(gè)實(shí)例:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
?return this.property;
}
function SubType(){
?this.subproperty = false;
}
//繼承,使SubType的原型指向SuperType的原型
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
?return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue());//true,實(shí)際上是SuperType的值
看以看出,單獨(dú)使用原型鏈實(shí)現(xiàn)繼承,實(shí)際上生成的實(shí)例都是共享一份屬性或者方法的。
那么,實(shí)例就沒有意義了。
那如果借用構(gòu)造函數(shù)呢?
我們知道,構(gòu)造函數(shù)只能實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承,沒辦法實(shí)現(xiàn)方法的繼承,所以如果單獨(dú)使用構(gòu)造函數(shù)模式,方法就需要都寫在構(gòu)造函數(shù)里面,函數(shù)復(fù)用就無從談起了。
組合繼承
組合繼承借用了原型鏈實(shí)現(xiàn)原型屬性和方法的繼承,又可以通過構(gòu)造函數(shù)實(shí)現(xiàn)實(shí)例屬性的繼承??梢哉f既實(shí)現(xiàn)了函數(shù)復(fù)用,又保證每個(gè)實(shí)例都有自己的屬性。
function SuperType(name){
?this.name = name;
?this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function(){
?console.log(this.name);
}
function SubType(name,age){
?SuperType.call(this,name);//在當(dāng)前實(shí)例環(huán)境下執(zhí)行SuperType構(gòu)造函數(shù)->2
?this.age = age;
}
SubType.prototype = new SuperType();//->1
SubType.prototype.sayAge = function(){
?console.log(this.age);
}
看以看到,這種模式結(jié)合了兩者的優(yōu)點(diǎn),又彌補(bǔ)了相互的不足??梢哉f,是一種不錯(cuò)的用于實(shí)現(xiàn)繼承的方法。
但是,這種方法并不是最好的,我們來詳細(xì)看一下:
1 為了實(shí)現(xiàn)繼承,第一點(diǎn)調(diào)用了SuperType的構(gòu)造函數(shù),這樣SubType.prototype繼承了兩個(gè)屬性
2 SubType構(gòu)造函數(shù)內(nèi)部,又調(diào)用了一次SubType構(gòu)造函數(shù),顯得有些多余了
寄生組合繼承
通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈來繼承方法。
function object(super){
?var func = function(){};
?func.prototype = super;
?var o = new func();
?return o;
}
function inheritPrototype(sub,super){
?var prototype = object(super.prototype);
?prototype.constructor = sub;
?sub.prototype = prototype;
}
這是寄生組合繼承的簡(jiǎn)單形式。
通過接受子構(gòu)造函數(shù)和父構(gòu)造函數(shù),第一步創(chuàng)建父類型原型的一個(gè)副本;
第二步為副本添加constructor屬性,最后再把新的原型賦給子原型,完美實(shí)現(xiàn)了繼承關(guān)系。