js 繼承

開(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é)果:


image.png

結(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)痢艺。

image.png

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)單的方法,使用callapply方法饶唤,將父對(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市望忆,隨后出現(xiàn)的幾起案子罩阵,更是在濱河造成了極大的恐慌竿秆,老刑警劉巖启摄,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異幽钢,居然都是意外死亡歉备,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)匪燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蕾羊,“玉大人,你說(shuō)我怎么就攤上這事帽驯」暝伲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵尼变,是天一觀的道長(zhǎng)利凑。 經(jīng)常有香客問(wèn)我浆劲,道長(zhǎng),這世上最難降的妖魔是什么哀澈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任牌借,我火速辦了婚禮,結(jié)果婚禮上割按,老公的妹妹穿的比我還像新娘膨报。我一直安慰自己,他們只是感情好适荣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布现柠。 她就那樣靜靜地躺著,像睡著了一般束凑。 火紅的嫁衣襯著肌膚如雪晒旅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天汪诉,我揣著相機(jī)與錄音废恋,去河邊找鬼。 笑死扒寄,一個(gè)胖子當(dāng)著我的面吹牛鱼鼓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该编,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼迄本,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了课竣?” 一聲冷哼從身側(cè)響起嘉赎,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎于樟,沒(méi)想到半個(gè)月后公条,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迂曲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年靶橱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片路捧。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡关霸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杰扫,到底是詐尸還是另有隱情队寇,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布章姓,位于F島的核電站佳遣,受9級(jí)特大地震影響炭序,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苍日,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一惭聂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧相恃,春花似錦辜纲、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至杀糯,卻和暖如春枪狂,著一層夾襖步出監(jiān)牢的瞬間剧罩,已是汗流浹背盏筐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工缴啡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骂际。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓疗琉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親歉铝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盈简,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • 一、原型與實(shí)例 原型是一個(gè)對(duì)象實(shí)例是用new來(lái)從原型對(duì)象生成的一個(gè)實(shí)例對(duì)象new命令后面跟的不是類太示,而是構(gòu)造函數(shù)柠贤。...
    sarah_wqq閱讀 477評(píng)論 0 1
  • 前端的繼承 關(guān)于前端的繼承臼勉,其實(shí)我一直都是有點(diǎn)模糊的,看了很多相關(guān)文章呀非,等去面試的時(shí)候別人一問(wèn)就混亂坚俗,所以想記錄一...
    晴晴要努力閱讀 395評(píng)論 0 1
  • // 定義一個(gè)動(dòng)物類function Animal (name) { // 屬性 this.name = name...
    SailingBytes閱讀 114評(píng)論 0 0
  • 繼承6種套餐 參照紅皮書(shū)镜盯,JS繼承一共6種 1.原型鏈繼承 核心思想:子類的原型指向父類的一個(gè)實(shí)例 Son.pro...
    燈不梨喵閱讀 3,115評(píng)論 1 2
  • 開(kāi)場(chǎng)白 大三下學(xué)期結(jié)束時(shí)候岸裙,一個(gè)人跑到帝都來(lái)參加各廠的面試,免不了的面試過(guò)程中經(jīng)常被問(wèn)到的問(wèn)題就是JS中如何實(shí)現(xiàn)繼...
    王豬猴閱讀 1,108評(píng)論 0 11