開(kāi)山例子
需求:學(xué)生交學(xué)費(fèi)清女,中學(xué)生打8折;小學(xué)生打5折谍失,然后通過(guò)打印的方法眶俩,顯示學(xué)生的名字、年齡記應(yīng)繳學(xué)費(fèi)快鱼。
我們初步的代碼:
<script>
// 中學(xué)生
function MidStu(name,age) {
this.name = name;
this.age = age;
this.show = function() {
alert(this.name + ' ' + this.age);
}
// 計(jì)算學(xué)費(fèi)
this.payFee = function(mon) {
alert("應(yīng)繳" + mon*0.8);
}
}
// 小學(xué)生
function PupStu(name,age) {
this.name = name;
this.age = age;
this.show = function() {
alert(this.name + ' ' + this.age);
}
// 計(jì)算學(xué)費(fèi)
this.payFee = function(mon) {
alert("應(yīng)繳" + mon*0.5);
}
}
</script>
可以看出兩個(gè)類型學(xué)生代碼中有很多相同的代碼颠印,出現(xiàn)了代碼冗余的問(wèn)題。
問(wèn)題:怎么解決上面的代碼冗余抹竹?
抽象出一個(gè)學(xué)生類(即线罕,把兩類學(xué)生共性取出來(lái)形成的類);然后通過(guò)繼承的方式處理窃判。
<script>
// 抽象出兩類學(xué)生共性的類
function Stu(name, age) {
this.name = name;
this.age = age;
this.show = function() {
alert(this.name + ' ' + this.age);
}
}
// 中學(xué)生
function MidStu(name,age) {
// js實(shí)際上是通過(guò)對(duì)象冒充來(lái)實(shí)現(xiàn)繼承的钞楼。
this.stu = Stu;
this.stu(name, age); // 這段代碼很重要,如果沒(méi)有它則繼承就失敗了
// 計(jì)算學(xué)費(fèi)
this.payFee = function(mon) {
alert("應(yīng)繳" + mon*0.8);
}
}
// 小學(xué)生
function PupStu(name,age) {
this.stu = Stu;
this.stu(name, age);
// 計(jì)算學(xué)費(fèi)
this.payFee = function(mon) {
alert("應(yīng)繳" + mon*0.5);
}
}
// 測(cè)試?yán)^承
var midStu = new MidStu("haha", 30);
midStu.show();
</script>
執(zhí)行結(jié)果:
結(jié)果我們發(fā)現(xiàn)袄琳,我們的MidStu中并沒(méi)show方法询件,但是我們結(jié)果卻能調(diào)用。
說(shuō)明:
js的繼承實(shí)際上是通過(guò)對(duì)象冒充來(lái)實(shí)現(xiàn)的唆樊。
js對(duì)象繼承的關(guān)鍵點(diǎn):
1雳殊、this.stu = Stu;這里相當(dāng)于將整個(gè)Stu函數(shù)賦值給stu窗轩;賦值的是內(nèi)存中的地址
夯秃。此時(shí)如果alert(this.stu)的話;會(huì)將Stu的整個(gè)函數(shù)打印出來(lái)
痢艺。
2仓洼、this.stu(‘xxx’, 20)
:這段代碼相當(dāng)重要,關(guān)乎繼承能否成功堤舒,前面我們知道色建,第一步只是接收了Stu的內(nèi)存中的地址,這步的執(zhí)行舌缤,就相當(dāng)于將Stu的屬性和方法箕戳,具體的賦給MidStu。
問(wèn)題:為什么需要繼承国撵?
最直接的回答陵吸,解決代碼的冗余,避免重復(fù)寫(xiě)代碼介牙。
對(duì)象之間繼承方法
比如壮虫,現(xiàn)在有一個(gè)"動(dòng)物"對(duì)象的構(gòu)造函數(shù)。
function Animal(){
this.species = "動(dòng)物";
}
還有一個(gè)"貓"對(duì)象的構(gòu)造函數(shù)环础。
function Cat(name,color){
this.name = name;
this.color = color;
}
怎樣才能使"貓"繼承"動(dòng)物"呢囚似?
一剩拢、來(lái)構(gòu)造函數(shù)綁定
第一種方法也是最簡(jiǎn)單的方法,使用call
或apply
方法饶唤,將父對(duì)象的構(gòu)造函數(shù)綁定在子對(duì)象上徐伐,即在子對(duì)象構(gòu)造函數(shù)中加一行:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
二、prototype模式
如果"貓"的prototype對(duì)象募狂,指向一個(gè)Animal的實(shí)例呵晨,那么所有"貓"的實(shí)例,就能繼承Animal了熬尺。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
代碼的第一行,我們將Cat的prototype對(duì)象指向一個(gè)Animal的實(shí)例谓罗。
Cat.prototype = new Animal();
它相當(dāng)于完全刪除了prototype 對(duì)象原先的值粱哼,然后賦予一個(gè)新值。但是檩咱,第二行又是什么意思呢揭措?
Cat.prototype.constructor = Cat;
原來(lái),任何一個(gè)prototype對(duì)象都有一個(gè)constructor
屬性刻蚯,指向它的構(gòu)造函數(shù)绊含。如果沒(méi)有"Cat.prototype = new Animal();"這一行,Cat.prototype.constructor是指向Cat的炊汹;加了這一行以后躬充,Cat.prototype.constructor指向Animal。
alert(Cat.prototype.constructor == Animal); //true
更重要的是讨便,每一個(gè)實(shí)例也有一個(gè)constructor屬性充甚,默認(rèn)調(diào)用prototype對(duì)象的constructor屬性。
alert(cat1.constructor == Cat.prototype.constructor); // true
因此霸褒,在運(yùn)行"Cat.prototype = new Animal();"這一行之后伴找,cat1.constructor也指向Animal!
alert(cat1.constructor == Animal); // true
這顯然會(huì)導(dǎo)致繼承鏈的紊亂(cat1明明是用構(gòu)造函數(shù)Cat生成的)废菱,因此我們必須手動(dòng)糾正技矮,將Cat.prototype對(duì)象的constructor值改為Cat。這就是第二行的意思殊轴。
注意:
這是很重要的一點(diǎn)衰倦,編程時(shí)務(wù)必要遵守。下文都遵循這一點(diǎn)旁理,即如果替換了prototype對(duì)象耿币。
o.prototype = {};
那么,下一步必然是為新的prototype對(duì)象加上constructor屬性韧拒,并將這個(gè)屬性指回原來(lái)的構(gòu)造函數(shù)淹接。
o.prototype.constructor = o;
三十性、 直接繼承prototype
第三種方法是對(duì)第二種方法的改進(jìn)。由于Animal對(duì)象中塑悼,不變的屬性都可以直接寫(xiě)入Animal.prototype
劲适。所以,我們也可以讓Cat()跳過(guò) Animal()厢蒜,直接繼承Animal.prototype霞势。
function Animal(){ }
Animal.prototype.species = "動(dòng)物";
說(shuō)明:
這里因?yàn)锳nimal類中的species是固定的數(shù)據(jù),因此我們使用prototype斑鸦,直接將其繼承在Animal類中愕贡。
然后,將Cat的prototype對(duì)象巷屿,然后指向Animal的prototype對(duì)象固以,這樣就完成了繼承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
注意:
與前一種方法相比嘱巾,這樣做的優(yōu)點(diǎn)是效率比較高(不用執(zhí)行和建立Animal的實(shí)例了)憨琳,比較省內(nèi)存。缺點(diǎn)是 Cat.prototype和Animal.prototype現(xiàn)在指向了同一個(gè)對(duì)象旬昭,那么任何對(duì)Cat.prototype的修改篙螟,都會(huì)反映到Animal.prototype。
所以问拘,上面這一段代碼其實(shí)是有問(wèn)題的遍略。請(qǐng)看第二行。
Cat.prototype.constructor = Cat;
這一句實(shí)際上把Animal.prototype對(duì)象的constructor屬性也改掉了骤坐。
alert(Animal.prototype.constructor); // Cat
四墅冷、 利用空對(duì)象作為中介
由于"直接繼承prototype"存在上述的缺點(diǎn),所以就有第四種方法或油,利用一個(gè)空對(duì)象作為中介寞忿。
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F是空對(duì)象,所以幾乎不占內(nèi)存顶岸。這時(shí)腔彰,修改Cat的prototype對(duì)象,就不會(huì)影響到Animal的prototype對(duì)象辖佣。
alert(Animal.prototype.constructor); // Animal
我們將上面的方法霹抛,封裝成一個(gè)函數(shù),便于使用卷谈。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
使用的時(shí)候杯拐,方法如下:
extend(Cat,Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
這個(gè)extend函數(shù),就是YUI庫(kù)如何實(shí)現(xiàn)繼承的方法。
另外端逼,說(shuō)明一點(diǎn)朗兵,函數(shù)體最后一行
Child.uber = Parent.prototype;
意思是為子對(duì)象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對(duì)象的prototype屬性顶滩。(uber是一個(gè)德語(yǔ)詞余掖,意思是"向上"、"上一層"礁鲁。)這等于在子對(duì)象上打開(kāi)一條通道盐欺,可以直接調(diào)用父對(duì)象的方法。這一行放在這里仅醇,只是為了實(shí)現(xiàn)繼承的完備性冗美,純屬備用性質(zhì)。
五析二、拷貝繼承
上面是采用prototype對(duì)象粉洼,實(shí)現(xiàn)繼承。我們也可以換一種思路甲抖,純粹采用"拷貝"方法實(shí)現(xiàn)繼承。簡(jiǎn)單說(shuō)心铃,如果把父對(duì)象的所有屬性和方法准谚,拷貝進(jìn)子對(duì)象,不也能夠?qū)崿F(xiàn)繼承嗎去扣?這樣我們就有了第五種方法柱衔。
首先,還是把Animal的所有不變屬性愉棱,都放到它的prototype對(duì)象上唆铐。
function Animal(){}
Animal.prototype.species = "動(dòng)物";
然后,再寫(xiě)一個(gè)函數(shù)奔滑,實(shí)現(xiàn)屬性拷貝的目的艾岂。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
這個(gè)函數(shù)的作用,就是將父對(duì)象的prototype對(duì)象中的屬性朋其,一一拷貝給Child對(duì)象的prototype對(duì)象王浴。
使用的時(shí)候,這樣寫(xiě):
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
非構(gòu)造函數(shù)的繼承
一梅猿、什么是"非構(gòu)造函數(shù)"的繼承氓辣?
比如,現(xiàn)在有一個(gè)對(duì)象袱蚓,叫做"中國(guó)人"钞啸。
var Chinese = {
nation:'中國(guó)'
};
還有一個(gè)對(duì)象,叫做"醫(yī)生"。
var Doctor ={
career:'醫(yī)生'
}
請(qǐng)問(wèn)怎樣才能讓"醫(yī)生"去繼承"中國(guó)人"体斩,也就是說(shuō)梭稚,我怎樣才能生成一個(gè)"中國(guó)醫(yī)生"的對(duì)象?
這里要注意硕勿,這兩個(gè)對(duì)象都是普通對(duì)象哨毁,不是構(gòu)造函數(shù),無(wú)法使用構(gòu)造函數(shù)方法實(shí)現(xiàn)"繼承"源武。
二扼褪、object()方法
json格式的發(fā)明人Douglas Crockford,提出了一個(gè)object()函數(shù)粱栖,可以做到這一點(diǎn)话浇。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
這個(gè)object()函數(shù),其實(shí)只做一件事闹究,就是把子對(duì)象的prototype屬性幔崖,指向父對(duì)象,從而使得子對(duì)象與父對(duì)象連在一起渣淤。
使用的時(shí)候赏寇,第一步先在父對(duì)象的基礎(chǔ)上,生成子對(duì)象:
var Doctor = object(Chinese);
然后价认,再加上子對(duì)象本身的屬性:
Doctor.career = '醫(yī)生';
這時(shí)嗅定,子對(duì)象已經(jīng)繼承了父對(duì)象的屬性了。
alert(Doctor.nation); //中國(guó)
三用踩、淺拷貝
除了使用"prototype鏈"以外渠退,還有另一種思路:把父對(duì)象的屬性,全部拷貝給子對(duì)象脐彩,也能實(shí)現(xiàn)繼承碎乃。
下面這個(gè)函數(shù),就是在做拷貝:
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
使用的時(shí)候惠奸,這樣寫(xiě):
var Doctor = extendCopy(Chinese);
Doctor.career = '醫(yī)生';
alert(Doctor.nation); // 中國(guó)
但是梅誓,這樣的拷貝有一個(gè)問(wèn)題。那就是佛南,如果父對(duì)象的屬性等于數(shù)組或另一個(gè)對(duì)象证九,那么實(shí)際上,子對(duì)象獲得的只是一個(gè)內(nèi)存地址共虑,而不是真正拷貝愧怜,因此存在父對(duì)象被篡改的可能。
請(qǐng)看妈拌,現(xiàn)在給Chinese添加一個(gè)"出生地"屬性拥坛,它的值是一個(gè)數(shù)組蓬蝶。
Chinese.birthPlaces = ['北京','上海','香港'];
通過(guò)extendCopy()函數(shù),Doctor繼承了Chinese猜惋。
var Doctor = extendCopy(Chinese);
然后丸氛,我們?yōu)镈octor的"出生地"添加一個(gè)城市:
Doctor.birthPlaces.push('廈門(mén)');
發(fā)生了什么事?Chinese的"出生地"也被改掉了
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門(mén)
alert(Chinese.birthPlaces); //北京, 上海, 香港, 廈門(mén)
所以著摔,extendCopy()只是拷貝基本類型的數(shù)據(jù)缓窜,我們把這種拷貝叫做
"淺拷貝"
。這是早期jQuery實(shí)現(xiàn)繼承的方式谍咆。
四禾锤、深拷貝
所謂"深拷貝",就是能夠?qū)崿F(xiàn)真正意義上的數(shù)組和對(duì)象的拷貝摹察。它的實(shí)現(xiàn)并不難恩掷,只要遞歸調(diào)用"淺拷貝"就行了。
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
使用的時(shí)候這樣寫(xiě):
var Doctor = deepCopy(Chinese);
現(xiàn)在供嚎,給父對(duì)象加一個(gè)屬性黄娘,值為數(shù)組。然后克滴,在子對(duì)象上修改這個(gè)屬性:
Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('廈門(mén)');
這時(shí)逼争,父對(duì)象就不會(huì)受到影響了。
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門(mén)
alert(Chinese.birthPlaces); //北京, 上海, 香港
目前劝赔,jQuery庫(kù)使用的就是這種繼承方法誓焦。
參考地址:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html