理解原型鏈

在JS中经柴,原型鏈有時候讓人覺得很胡里花哨挟憔,又是prototype钟些、proto又是各種指向什么的,讓人覺得很頭疼绊谭。如果你也有這種感覺政恍,或許這篇文章可以幫助到你

一、認(rèn)識原型

1达传、先來一串代碼

var Person = function(msg){
    this.msg = msg;
}
var person1 = new Person("wanger")

person1.constructor===Person;    //true
Person === Person.prototype.constructor; //true
person1.__proto__ === Person.prototype; //true
person1.__proto__.constructor === person1.constructor //true

看暈了吧篙耗?是不是很胡里花哨?不用擔(dān)心宪赶,其實一張圖就能了明白這其中的關(guān)系:

Alt text
Alt text
  • 藍(lán)色的是構(gòu)造函數(shù)
  • 綠色的是構(gòu)造函數(shù)實例出來的對象
  • 橙色的是構(gòu)造函數(shù)的prototype,也是構(gòu)造函數(shù)實例出來的對象的原型(它其實也是一個對象)

2宗弯、這里特別要注意的是prototype__proto__的區(qū)別,prototype是函數(shù)才有的屬性搂妻,而__proto__是每個對象都有的屬性蒙保。(__proto__不是一個規(guī)范屬性,只是部分瀏覽器實現(xiàn)了此屬性,對應(yīng)的標(biāo)準(zhǔn)屬性是[[Prototype]])。

二震捣、認(rèn)識原型鏈

1、我們剛剛了解了原型,那原型鏈在哪兒呢懈糯?不要著急涤妒,再上一張圖:

Alt text
Alt text

通過這張圖我們可以了解到,person1的原型鏈?zhǔn)牵?/p>

person1 ----> Person.prototype ----> Object.prototype ----> null

2、事實上赚哗,函數(shù)也是一個對象她紫,所以硅堆,Person的原型鏈?zhǔn)牵?/p>

Person ----> Function.prototype ----> Object.prototype ----> null

由于Function.prototype定義了apply()等方法,因此贿讹,Person就可以調(diào)用apply()方法渐逃。

3、如果把原型鏈的關(guān)系都顯示清楚民褂,那會復(fù)雜一些茄菊,如下圖:

Alt text
Alt text

這里需要特別注意的是:所有函數(shù)的原型都是Function.prototype,包括Function構(gòu)造函數(shù)和Object構(gòu)造函數(shù)(如圖中的標(biāo)紅部分)

三、原型鏈的繼承

1赊堪、假設(shè)我們要基于Person擴(kuò)展出Student面殖,Student的構(gòu)造如下:

function Student(props) {
    // 調(diào)用Person構(gòu)造函數(shù),綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}

但是哭廉,調(diào)用了Person構(gòu)造函數(shù)不等于繼承了Person脊僚,Student創(chuàng)建的對象的原型是:

new Student() ----> Student.prototype ----> Object.prototype ----> null

示意圖如下所示:

Alt text
Alt text

必須想辦法把原型鏈修改為:

new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null

示意圖如下所示:

Alt text
Alt text

那我們應(yīng)該怎么修改呢?仔細(xì)觀察兩張圖的差異遵绰,我們會發(fā)現(xiàn)辽幌,如果我們將Studentprototype改成person1對象不就大功告成了?于是有了下面的代碼:

Student.prototype = person1 ;

但是這時候有個問題:

Student.prototype.constructor === Student; //false

原來Student.prototype(即person1)的constructor指向的還是Person椿访,這時候還需要我們再改一下代碼:

Student.prototype.constructor = Student;

這樣就能把Student的原型鏈順利的修改為: new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null 了乌企;

完整的代碼顯示如下:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調(diào)用Person構(gòu)造函數(shù),綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
var person1 = new Person("wanger")
Student.prototype = person1 ;
Student.prototype.constructor = Student;

三赎离、用以上原型鏈繼承帶來的問題

1逛犹、如果在控制臺執(zhí)行一遍上述的代碼,我們會發(fā)現(xiàn)一些問題梁剔,如圖所示:

Alt text
Alt text

Student.prototype上含有之前person1帶有的屬性虽画,那么,這樣的繼承的方法就顯得不那么完美了

2荣病、這個時候码撰,我們可以借助一個中間對象來實現(xiàn)正確的原型鏈,這個中間對象的原型要指向Person.prototype个盆。為了實現(xiàn)這一點脖岛,參考道爺(就是發(fā)明JSON的那個道格拉斯)的代碼,中間對象可以用一個空函數(shù)F來實現(xiàn):

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調(diào)用Person構(gòu)造函數(shù)颊亮,綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}

// 空函數(shù)F:
function F() {
}

// 把F的原型指向Person.prototype:
F.prototype = Person.prototype;

// 把Student的原型指向一個新的F對象柴梆,F(xiàn)對象的原型正好指向Person.prototype:
Student.prototype = new F();

// 把Student原型的構(gòu)造函數(shù)修復(fù)為Student:
Student.prototype.constructor = Student;

// 繼續(xù)在Student原型(就是new F()對象)上定義方法:
Student.prototype.getGrade = function () {
    return this.grade;
};

// 創(chuàng)建wanger:
var wanger = new Student({
    name: '王二',
    grade: 9
});
wanger.msg; // '王二'
wanger.grade; // 9

// 驗證原型:
wanger.__proto__ === Student.prototype; // true
wanger.__proto__.__proto__ === Person.prototype; // true

// 驗證繼承關(guān)系:
wanger instanceof Student; // true
wanger instanceof Person; // true

這其中主要用到了一個空函數(shù)F作為過橋函數(shù)。為什么道爺會用過橋函數(shù)终惑?用過橋函數(shù)F(){}主要是為了清空構(gòu)造的屬性绍在。如果有些原Person的構(gòu)造用不到,那么過橋函數(shù)將是一個好的解決方案

這樣寫的話,Student.prototype上就沒有任何自帶的私有屬性偿渡,這是理想的繼承的方法

3臼寄、如果把繼承這個動作用一個inherits()函數(shù)封裝起來,還可以隱藏F的定義溜宽,并簡化代碼:

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

封裝后吉拳,寫起來就像這樣:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調(diào)用Person構(gòu)造函數(shù),綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
inherits(Student,Person) ;

這樣再一封裝的話适揉,代碼就很完美了留攒。

事實上,我們也可以在inherits中使用Object.create()來進(jìn)行操作涡扼,代碼如下:

function inherits(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

如果有興趣了解Object.create()的其他用法稼跳,可以參考我的這篇博客JS中Object.create的使用方法;

四、ES6的新關(guān)鍵字class

在ES6中吃沪,新的關(guān)鍵字class汤善,extends被正式被引入,它采用的類似java的繼承寫法票彪,寫起來就像這樣:

class Student extends Person {
    constructor(name, grade) {
        super(msg); // 記得用super調(diào)用父類的構(gòu)造方法!
        this.grade = grade || 1;
    }
    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

這樣寫的話會更通俗易懂红淡,繼承也相當(dāng)方便。讀者可以進(jìn)入廖雪峰的官方網(wǎng)站詳細(xì)了解class的用法

參考文獻(xiàn):廖雪峰的官方網(wǎng)站
原文地址:王玉略的個人網(wǎng)站

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末降铸,一起剝皮案震驚了整個濱河市在旱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌推掸,老刑警劉巖桶蝎,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谅畅,居然都是意外死亡登渣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門毡泻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胜茧,“玉大人,你說我怎么就攤上這事仇味∩胪纾” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵丹墨,是天一觀的道長廊遍。 經(jīng)常有香客問我,道長贩挣,這世上最難降的妖魔是什么喉前? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任英染,我火速辦了婚禮,結(jié)果婚禮上被饿,老公的妹妹穿的比我還像新娘。我一直安慰自己搪搏,他們只是感情好狭握,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疯溺,像睡著了一般论颅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上囱嫩,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天恃疯,我揣著相機(jī)與錄音,去河邊找鬼墨闲。 笑死今妄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸳碧。 我是一名探鬼主播盾鳞,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞻离!你這毒婦竟也來了腾仅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤套利,失蹤者是張志新(化名)和其女友劉穎推励,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肉迫,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡验辞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了昂拂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片受神。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖格侯,靈堂內(nèi)的尸體忽然破棺而出鼻听,到底是詐尸還是另有隱情,我是刑警寧澤联四,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布撑碴,位于F島的核電站,受9級特大地震影響朝墩,放射性物質(zhì)發(fā)生泄漏醉拓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亿卤。 院中可真熱鬧愤兵,春花似錦、人聲如沸排吴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钻哩。三九已至屹堰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間街氢,已是汗流浹背扯键。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留珊肃,地道東北人荣刑。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像近范,于是被迫代替她去往敵國和親嘶摊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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