JS核心基礎(chǔ)知識(shí)總結(jié)(一)——原型和繼承

原型/原型鏈

JS是一門基于原型實(shí)現(xiàn)繼承的語(yǔ)言。那么举庶,什么是原型?基于原型實(shí)現(xiàn)的繼承又是怎么一回事揩抡?

原型(prototype)户侥,根據(jù)字面意思镀琉,可以理解為一件事物的模板。比如iPhone的原型是以前只能打電話蕊唐、發(fā)短信的功能機(jī)屋摔,這表示,iPhone也擁有打電話替梨、發(fā)短信的功能(繼承)钓试,但是相比它的原型又擁有了更多功能(可以擴(kuò)展更多功能)。這種關(guān)系有點(diǎn)類似于Java中的子類與父類的關(guān)系(Java是基于類的繼承副瀑,而Javascript是基于原型)弓熏。

在JS中,每一個(gè)函數(shù)(Function)都有一個(gè)prototype屬性糠睡,這個(gè)prototype對(duì)象有一個(gè)屬性constructor指向這個(gè)構(gòu)造函數(shù):

function Person() {
?
}
Person.prototype.constructor === Person // true

另外挽鞠,每個(gè)JS對(duì)象還有一個(gè)隱藏屬性proto,指向它的構(gòu)造函數(shù)的原型對(duì)象(prototype)狈孔,即:

var person = new Person();
person._proto_ === Person.prototype; // true

對(duì)象實(shí)例信认、構(gòu)造函數(shù)和原型的關(guān)系可以表示成下圖:


prototype3.png

更進(jìn)一步的,我們知道Person.prototype也是一個(gè)對(duì)象均抽,那么它也擁有proto屬性嫁赏,指向Person.prototype構(gòu)造函數(shù)的原型對(duì)象,這個(gè)原型對(duì)象又有proto屬性…...通過(guò)這樣一個(gè)實(shí)例對(duì)象的proto指向構(gòu)造函數(shù)prototype油挥、prototype對(duì)象又擁有proto屬性的指向循環(huán)潦蝇,我們就可以建立起一條原型鏈。

person._proto_ => Person.prototype
Person.prototype._proto_ => Object.prototype
Object.prototype._proto_ === null // true

注意喘漏,所有原型鏈的終點(diǎn)是Object.prototype.proto护蝶,這個(gè)對(duì)象沒(méi)有對(duì)應(yīng)構(gòu)造函數(shù)的原型了,所以為null翩迈。

基于原型對(duì)象(prototype)和原型鏈持灰,我們就可以實(shí)現(xiàn)繼承。

繼承

按照《Javascript高級(jí)程序設(shè)計(jì)》中所寫负饲,在JS中實(shí)現(xiàn)繼承大致有6種方式:

一堤魁、借用構(gòu)造函數(shù)

子類型要如何擁有父類型的屬性呢?如果父類的屬性都是通過(guò)構(gòu)造函數(shù)定義的返十,那么最簡(jiǎn)單粗暴的方法當(dāng)然是直接在子類的構(gòu)造函數(shù)中調(diào)用父類的構(gòu)造函數(shù)妥泉,此時(shí)子類就具有了父類的所有屬性。

function SuperType(name) {
    this.name = name;
    this.colors = ['pink', 'blue', 'green'];
}
function SubType(name) {
    SuperType.call(this, name);
}
let instance1 = new SubType('Yvette');
instance1.colors.push('yellow');
console.log(instance1.colors);//['pink', 'blue', 'green', yellow]

let instance2 = new SubType('Jack');
console.log(instance2.colors); //['pink', 'blue', 'green']

此時(shí)我們已經(jīng)讓SubType擁有了SuperType的屬性洞坑。

問(wèn)題來(lái)了盲链,如果我們想讓SubType能夠直接復(fù)用/繼承SuperType的方法,這種繼承的方式就無(wú)法實(shí)現(xiàn)了。因此這種方式是有缺陷的刽沾,在實(shí)踐中也不可能單純用它實(shí)現(xiàn)繼承本慕。

此時(shí)就需要我們前面鋪墊了很久的原型對(duì)象出場(chǎng)了。

二侧漓、原型鏈繼承

在JS中讀取一個(gè)實(shí)例屬性時(shí)锅尘,首先會(huì)在該實(shí)例上讀取,如果讀取不到需要的屬性布蔗,則會(huì)在實(shí)例的原型上搜索藤违。那么如果我們讓A類型的原型指向另一個(gè)B類型,在A類型上讀取不到的屬性就可以接著去A類型的原型(也就是B類型)去讀取纵揍,更進(jìn)一步的會(huì)去搜過(guò)B類型的原型顿乒,一直到原型鏈的末端。這就是原型鏈繼承骡男。

因此我們想要SubType繼承SuperType淆游,只需要讓前者的prototype指向后者的實(shí)例就行了。

function SuperType() {
    this.name = 'Yvette';
    this.colors = ['pink', 'blue', 'green'];
}
SuperType.prototype.getName = function () {
    return this.name;
}
function SubType() {
    this.age = 22;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function() {
    return this.age;
}
SubType.prototype.constructor = SubType;
let instance1 = new SubType();
instance1.colors.push('yellow');
console.log(instance1.getName()); //'Yvette'
console.log(instance1.colors);//[ 'pink', 'blue', 'green', 'yellow' ]

let instance2 = new SubType();
console.log(instance2.colors);//[ 'pink', 'blue', 'green', 'yellow' ] 隔盛,注意這里instance2的屬性和instanse1共享了

此時(shí)我們不僅可以繼承父類的屬性犹菱,函數(shù)也獲得了繼承。

但是這樣實(shí)現(xiàn)繼承的缺陷在于——所有實(shí)例的屬性都共享了吮炕。這顯然也是不可接受的腊脱,我們想要每個(gè)實(shí)例能有自己的屬性,只用繼承同樣的函數(shù)就行了龙亲。此時(shí)我們會(huì)想到:借用構(gòu)造函數(shù)的繼承可以使每個(gè)實(shí)例擁有自己的屬性陕凹,而原型鏈繼承可以繼承父類的函數(shù),能不能把二者的優(yōu)點(diǎn)集合起來(lái)呢鳄炉?

三杜耙、組合繼承(借用構(gòu)造函數(shù) + 原型鏈繼承)

我們可以使用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)屬性的繼承,再用原型鏈實(shí)現(xiàn)對(duì)方法的繼承拂盯,綜合二者各自的優(yōu)點(diǎn)就能實(shí)現(xiàn)一個(gè)功能完善的繼承了佑女。

function SuperType(name) {
    this.name = name;
    this.colors = ['pink', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
}
function SuberType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
SuberType.prototype = new SuperType();
SuberType.prototype.constructor = SuberType;
SuberType.prototype.sayAge = function () {
    console.log(this.age);
}
let instance1 = new SuberType('Yvette', 20);
instance1.colors.push('yellow');
console.log(instance1.colors); //[ 'pink', 'blue', 'green', 'yellow' ]
instance1.sayName(); //Yvette

let instance2 = new SuberType('Jack', 22);
console.log(instance2.colors); //[ 'pink', 'blue', 'green' ]
instance2.sayName();//Jack

如上所示,每個(gè)子類的實(shí)例都擁有了自己的屬性谈竿,并且都繼承了父類的sayName方法(注意父類的方法是定義在prototype對(duì)象上的)团驱。

這個(gè)方案已經(jīng)基本能在實(shí)踐中使用了,但是它還是有一個(gè)小問(wèn)題——父類的構(gòu)造函數(shù)調(diào)用了2次空凸。一次是在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)嚎花,另一次是在把子類的原型用父類的一個(gè)實(shí)例賦值時(shí)。理論上還是有優(yōu)化的空間呀洲。

現(xiàn)在我們?cè)賮?lái)看看另一種繼承的實(shí)現(xiàn)紊选,也是一種很有名的實(shí)現(xiàn)啼止。

四、原型式繼承

借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象丛楚,不必因此創(chuàng)建新類型族壳。我們來(lái)看一個(gè)函數(shù):

function object(o) {
    function F() { }
    F.prototype = o;
    return new F();
}

object()函數(shù)內(nèi)部創(chuàng)建了一個(gè)臨時(shí)類型F,然后將傳入的對(duì)象作為它的原型并返回了一個(gè)實(shí)例。本質(zhì)上相當(dāng)于對(duì)傳入的對(duì)象進(jìn)行了一次淺拷貝趣些。實(shí)踐中我們不需要手寫這個(gè)函數(shù),而是可以直接使用Object.create()來(lái)實(shí)現(xiàn)同樣的功能贰您。

在沒(méi)有必要?jiǎng)?chuàng)建單獨(dú)的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)一些定制功能坏平,只是需要讓兩個(gè)對(duì)象的行為保持一致時(shí),我們可以使用這樣的原型式繼承锦亦。

當(dāng)然它也具有原型鏈繼承的缺點(diǎn)舶替,無(wú)法為每個(gè)實(shí)例創(chuàng)建自己獨(dú)有的屬性。

五杠园、寄生式繼承

寄生式繼承是基于原型式繼承的顾瞪,只不過(guò)在創(chuàng)建對(duì)象的過(guò)程中以某種方式對(duì)它進(jìn)行了一些增強(qiáng)。

function createAnother(original) {
    var clone = object(original);// 通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi = function () {// 以某種方式增強(qiáng)這個(gè)對(duì)象
        console.log('hi');
    };
    return clone;// 返回這個(gè)對(duì)象
}
var person = {
    name: 'Yvette',
    hobbies: ['reading', 'photography']
};

var person2 = createAnother(person);
person2.sayHi(); //hi

但是依然沒(méi)有解決原型式繼承的問(wèn)題抛蚁。

根據(jù)前面提到的組合繼承的思路陈醒,我們?cè)僖淮嗡伎寄芊袷褂媒M合多種方案來(lái)解決問(wèn)題。

六瞧甩、寄生組合式繼承

通過(guò)名字我們大概能猜到钉跷,這種方式是組合了寄生式繼承、借用構(gòu)造函數(shù)的方式肚逸。

首先我們通過(guò)寄生式繼承實(shí)現(xiàn)方法的繼承:

function inherit(superType, subType) {
  var o = object(superType.prototype);
  o.constructor = subType; // 注意這一步——維持constructor是subType爷辙,因?yàn)樯弦恍袑rototype設(shè)為了superType
  subType.prototype = o;
}

接著我們補(bǔ)充構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承:

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'yellow'];
}

SuperType.prototype.sayName = function () {
  alert(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name); //只調(diào)用了一次構(gòu)造函數(shù)
  
  this.age = age;
}

inherit(SuperType, SubType);

......

現(xiàn)在我們實(shí)現(xiàn)了一個(gè)較為完善的繼承:

它既能實(shí)現(xiàn)方法的復(fù)用,又能保證每個(gè)實(shí)例擁有各自的屬性朦促,同時(shí)它只調(diào)用了一次構(gòu)造函數(shù)膝晾,因?yàn)槲覀儧](méi)有像原型鏈繼承一樣創(chuàng)建額外的一個(gè)父類型的實(shí)例給子類型的原型。

這也就是我們實(shí)踐中可以使用的一種繼承的實(shí)現(xiàn)方式务冕。

ES 6的繼承

ES 6中新增了classextends關(guān)鍵字血当,可以讓我們?cè)贘S中實(shí)現(xiàn)其他基于類的繼承的語(yǔ)言的繼承寫法。

class SubType extends SuperType {
  
}

當(dāng)然雖然我們可以用如此簡(jiǎn)潔的寫法完成繼承洒疚,實(shí)際上底層實(shí)現(xiàn)仍然是基于原型實(shí)現(xiàn)的歹颓,只不過(guò)Babel幫我們完成了這部分轉(zhuǎn)譯工作。而轉(zhuǎn)譯出的代碼實(shí)質(zhì)上和我們上面所寫的寄生組合式繼承的代碼是大同小異的油湖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巍扛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乏德,更是在濱河造成了極大的恐慌撤奸,老刑警劉巖吠昭,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胧瓜,居然都是意外死亡矢棚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門府喳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蒲肋,“玉大人,你說(shuō)我怎么就攤上這事钝满《嫡常” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵弯蚜,是天一觀的道長(zhǎng)孔轴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碎捺,這世上最難降的妖魔是什么路鹰? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮收厨,結(jié)果婚禮上晋柱,老公的妹妹穿的比我還像新娘。我一直安慰自己帽氓,他們只是感情好趣斤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黎休,像睡著了一般浓领。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上势腮,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天联贩,我揣著相機(jī)與錄音,去河邊找鬼捎拯。 笑死泪幌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的署照。 我是一名探鬼主播祸泪,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼建芙!你這毒婦竟也來(lái)了没隘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤禁荸,失蹤者是張志新(化名)和其女友劉穎右蒲,沒(méi)想到半個(gè)月后阀湿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑰妄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年陷嘴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片间坐。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灾挨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竹宋,到底是詐尸還是另有隱情涨醋,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布逝撬,位于F島的核電站,受9級(jí)特大地震影響乓土,放射性物質(zhì)發(fā)生泄漏宪潮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一趣苏、第九天 我趴在偏房一處隱蔽的房頂上張望狡相。 院中可真熱鬧,春花似錦食磕、人聲如沸尽棕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滔悉。三九已至,卻和暖如春单绑,著一層夾襖步出監(jiān)牢的瞬間回官,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工搂橙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斋日,地道東北人止喷。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親飒赃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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