js原型和原型鏈(用代碼理解代碼)

眾所周知js原型及原型鏈?zhǔn)呛芏嚅_發(fā)者的一個疼點(我也不例外)沽损,我也曾多次被問起灯节,也問過不少其他人,如果在自己沒有真正的去實踐和理解過缠俺;那么突然之間要去用最簡單的話語進行概述還真不是一件容易的事情显晶;
其實工作中看似神秘的js原型也并不是那么難以理解,最終其目的無非是為了達到方法壹士、屬性共享代碼重用的目的磷雇;在我所了解的編程語言中都會用到object這個最頂層對象,然而生命的法則最終是從無到有躏救,就如同世界先有雞還是先有蛋一樣唯笙。

一、 Class

先來看一個簡單的es6的例子吧

假如我們定義一個類


/*用于全局狀態(tài)管理*/
class State{}

然后在某個頁面為其State.draw = true 然后在項目的任何地方都能使用State.draw來獲取其值盒使;而且當(dāng)你一個地方更改崩掘,其它頁面也會同時獲取到更改后的值


class State{
    constructor(draw){
        this.draw = draw;
    }
}

然而使用對象屬性方式就不一樣了;


var state1 = new State('bar');
var state2 = new State('pie');
state2.draw = 'hello'; // 不管state2 的draw 怎么改都不會影響到 state1 的屬性值

為啥扯上了這么一大圈少办,這個和原型好像沒關(guān)系苞慢,其實是有的,State.draw 就等同于 State.prototype.draw = true英妓;
this.draw = draw;就等同于 普通的函數(shù)賦值

 function Sate(){
       this.draw = draw;
}

二挽放、普通 function


function Sate(draw){
    this.draw = draw;
}
var state1 = new State('bar');
var state2 = new State('pie');
state2.draw = 'hello'; // 不管state2 的draw 怎么改都不會影響到 state1 的屬性值

使用node運行測試

C:\Users\Lenovo>node
> function State(draw){
... this.draw = draw;
... }
undefined
> var state1 = new State('bar');
undefined
> var state2 = new State('pie');
undefined
> console.log(state1.draw);
bar
undefined
> console.log(state2.draw);
pie
undefined
> state2.draw = 'circle';
'circle'
> console.log(state1.draw);
bar
undefined
> console.log(state2.draw);
circle
undefined
>                                                                                                                                 

柑橘和第一個Class案例是有點相同了,在題中state1 和 state2 完全是兩個不同的對象蔓纠,他們各自維護自己的屬性和方法和其他人沒得關(guān)系

> console.log(state1);
State { draw: 'bar' }
undefined
> console.log(state2);
State { draw: 'pie' }
undefined
>   

三 辑畦、原型(prototype)

function State(){}
State.prototype.draw = 'pie';

var state1 = new State();
var state2 = new State();

console.log(state1.draw);
console.log(state2.draw);

state2.draw = 'bar'; 

console.log(state1.draw);
console.log(state2.draw);

使用node運行測試

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.draw = 'pie';
'pie'
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> console.log(state1.draw);
pie
undefined
> console.log(state2.draw);
pie
undefined
> console.log(state1); // 看:打印的 state1和 state2 是一個空的 State {} 那上面卻能正常打印draw
State {}
undefined
> console.log(state2);
State {}
undefined
>     

看:打印的 state1和 state2 是一個空的 State {} 那上面卻能正常打印draw;其實這里的state1 和state2 只是State 的一個引用腿倚,在實例本身是沒有任何屬性的纯出,但是他可以通過自身的__proto__關(guān)聯(lián)到Sate這個構(gòu)造函數(shù)


> console.log(state1.__proto__);
State { draw: 'pie' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie' }
undefined
>   
   

而注意的是這個屬性并沒有直接關(guān)聯(lián)構(gòu)造函數(shù);只是關(guān)聯(lián)了構(gòu)造函數(shù)的prototype屬性(原型對象)


> console.log(State);
[Function: State]
undefined
> console.log(State.prototype);
State { draw: 'pie' }
undefined
>               

從打印可以得出 State.prototype 就等同于了state2.__proto__

so state2.proto == State.prototype


> console.log(state2.__proto__ == State.prototype);
true
undefined
>      

四、原型的用途

然而說了半天暂筝,這個原型的用處究竟在哪里呢箩言?
其實他的主要用途就是 繼承(extends),代碼重用
如:


function State(){}
State.prototype.draw = 'pie';
State.prototype.drawGraphic  = function(){
    let height = 100;
    let width = 100;
    // draw a graphics...
    console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
}

上面定義了繪圖的構(gòu)造函數(shù)和方法焕襟,現(xiàn)在就可以開始使用了


var state1 = new State();
var state2 = new State();
var state3 = new State();
var state4 = new State();

// 使用實例化的四個實例分扎,調(diào)用drawGraphic進行圖形繪制
state1.drawGraphic();
state2.drawGraphic();
state3.drawGraphic();
state4.drawGraphic();

如上:實例化的四個實例,調(diào)用drawGraphic進行圖形繪制胧洒,
然而他們并沒有去創(chuàng)建各自的方法,只是直接從原型引用了State 上的drawGraphic墨状,這樣就極大的節(jié)約了開銷卫漫;
如果不使用原型的方式,這個四個對象將會創(chuàng)建四個對應(yīng)的方法肾砂,這就是一種極大浪費列赎。

如果不明白可以來看看看開始的例子

4.1 普通function, 實例化多個對象

C:\Users\Lenovo>node
> function State(draw){
... this.drawGraphic = function(){
..... let height = 100;
.....   let width = 100;
.....   // draw a graphics...
.....   console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
..... }
... };
undefined
> var state1 = new State('bar');
undefined
> var state2 = new State('pie');
undefined
> console.log(state1);
State { drawGraphic: [Function] }
undefined
> console.log(state2);
State { drawGraphic: [Function] }
undefined
>           

從運行可以看出每個new出來的實例都會創(chuàng)建屬于自己的實例方法和屬性

4.2 使用原型方式

只用從State 上面調(diào)用drawGraphic 方法,而不會自己再去創(chuàng)建镐确,就同java 的繼承一個意思包吝,直接從父類繼承方法,屬性源葫,然后使用诗越。看:

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.drawGraphic  = function(){
...     let height = 100;
...     let width = 100;
...     // draw a graphics...
...     console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
... }
[Function]
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> console.log(state1);
State {}
undefined
> console.log(state2);
State {}
undefined
> state1.drawGraphic();
Draw a graphic with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a graphic with a height of 100px and a width of 100px
undefined
>   

4.3 實例原型重寫

那有的童鞋就會問了你這個只能調(diào)一個方法打印同樣的圖形息堂,太死板了嚷狞;其實不然,原型也支持重寫(override)

改改剛才的案例

function State(){}
State.prototype.draw = 'pie';
State.prototype.drawGraphic  = function(){
    let height = 100;
    let width = 100;
    // draw a graphics...
    console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
}

使用node運行測試

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.draw = 'pie';
'pie'
> State.prototype.drawGraphic  = function(){
...     let height = 100;
...     let width = 100;
...     // draw a graphics...
...     console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
... };
[Function]
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> var state3 = new State();
undefined
> state1.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state3.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.draw = 'circle';
'circle'
> state1.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a circle with a height of 100px and a width of 100px
undefined
> state3.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
>   

看運行結(jié)果:state2將 draw 重新設(shè)置為了 circle 荣堰;再次調(diào)用打印 Draw a circle with a height of 100px and a width of 100px床未;然而他并沒有影響到其他的實例(其實說白了就是在執(zhí)行state2.draw = 'circle'; 在state2實例對象上新增了一個draw的屬性)


為了能更好理解繼承和重寫:來用我們小學(xué)老師教我的語文解釋一哈【

繼承如同你父親開了一家xxx公司,你就可以直接找財務(wù)開個20萬,今天要去約個女朋友吃飯振坚,然后財務(wù)一看原來是少爺呀薇搁,大筆一揮給你了,然后你再開上你父親的法拉利愉快的約會去了渡八。
當(dāng)某一天你發(fā)現(xiàn)自己也該干一番事業(yè)了啃洋,于是開始模仿你父親建起來了同樣的公司和相同的經(jīng)驗?zāi)J剑苍S有人會想干嘛不直接把父親公司改為自己的呀狼?
那肯定不行啦裂允,因為那樣老大,姥二哥艇,老三……不會把你打死呀绝编。最后只好自己模擬了個和你父親相同的xxx子公司,現(xiàn)在出去約會就直接叫自己財務(wù)開單了,然后開著自己的法拉利愉快的且十饥。
假如哪天經(jīng)驗不當(dāng)(女朋友太多窟勃,哈哈),又得去找你父親的財務(wù)了逗堵,那就不一樣了現(xiàn)在得明確指定是去父親的財務(wù)那里(super.財務(wù))還是自己的財務(wù)那里開支票秉氧;
否則你直接給你手下說去叫財務(wù)給我20萬,他第一反應(yīng)當(dāng)然是去找你自己公司的財務(wù)了蜒秤,啊哈哈汁咏。】


C:\Users\Lenovo>node
> console.log(state2);
State { draw: 'circle' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie', drawGraphic: [Function] }
undefined
>         

如果上面啥子重寫依然搞不清楚那可以把它看成(雖然不是太準(zhǔn)確作媚,為了理解還是可以的)
在執(zhí)行state2.draw = 'circle';是為該state2實例對象新增了一個draw的屬性攘滩,

那你可能會迷惑了打印是為啥會是circle 而不是pie呢,不是說好的state2 的原型指向State.prototype嗎纸泡,
State中的draw并沒有改變呀漂问,其實問題在于js 屬性查找機制(就近原則)

首先獲取屬性值會優(yōu)先從自己對象上面查找,當(dāng)對象沒有該屬性才會通過__proto__到原型對象上面去找那個屬性女揭;
假如該State.prototype原型對象上也沒有該屬性蚤假,他會再根據(jù)State.prototype.__proto__繼續(xù)向上,直到Object吧兔;直達Object.protopyte.__proto__磷仰,最后如果還沒有找到對于的屬性;那就給你個undefined了

4.4 原型對象重寫

那你說假如我就是想更改所有對象的draw咋辦呢掩驱。當(dāng)然有辦法(你想翻天誰都攔不住芒划,,哈哈)

> state2.__proto__.draw = 'trinagle';
'trinagle'
> console.log(state3.drawGraphic());
Draw a trinagle with a height of 100px and a width of 100px
undefined
undefined
> console.log(state2.drawGraphic());
Draw a circle with a height of 100px and a width of 100px
undefined
undefined
> console.log(state1.drawGraphic());
Draw a trinagle with a height of 100px and a width of 100px
undefined
undefined
>      

看到?jīng)]有state2.__proto__.draw = 'trinagle'一執(zhí)行欧穴,其他state1和state3就瞬間改變了打印為Draw a trinagle ...
state2沒有遭更改因為最開始他就重寫了一次(他自己有了自己的子公司)

五民逼、 原型鏈

說了半天沒講到原型鏈,不地道涮帘,其實上面都已經(jīng)出現(xiàn)過n次了拼苍,只是你沒注意(世上最遙遠的距離是:我就在你眼前你卻不認(rèn)識)哈哈扯遠了。

想想一個實例對象和函數(shù)對象是怎么取得聯(lián)系的调缨,不就是通過__proto__這個屬性(這個屬性會在函數(shù)對象或是普通對象一降生就會自帶而來)嗎疮鲫?

那就對了其實他作用就是建立實例與函數(shù)對象之間的一條鏈子簡稱原型鏈

注意這個__proto__ 是所有人都有(一視同仁);然而prototype這就就不一樣了只有函數(shù)對象才具備(只有大佬才具備弦叶,哈哈)

C:\Users\Lenovo>node
> function State(){}
undefined
> var state1 = new State();
undefined
> console.log(State.prototype);
State {}
undefined
> console.log(state1.prototype);
undefined
undefined
>     

看到了吧俊犯。


5.1 函數(shù)對象

現(xiàn)在來揭開原型鏈的神秘面紗了
接著看

C:\Users\Lenovo>node
> function State(){}
undefined
> var state1 = new State();
undefined
> console.log(State.prototype);
State {}
undefined
> console.log(state1.__proto__);
State {}
undefined
> console.log(State.prototype == state1.__proto__);
true
undefined

從上得出State.prototype == state1.__proto__ 證明 State.prototype也就如同State的一個實例

佐證一下:每一個實例對象都會默認(rèn)攜帶一個constructor屬性指向其構(gòu)造函數(shù),那么原型對象為什么也會有一個constructor屬性呢伤哺,其實他也是構(gòu)造函數(shù)的一個實例

undefined
> console.log(State.prototype.constructor);
[Function: State]
undefined
> console.log(state1.constructor)
[Function: State]
undefined
> console.log(state1.constructor == State.prototype.constructor)
true
undefined
>        

ok State和他的實例之間的愛恨情仇算是基本清楚了吧

不清楚再簡單畫一哈:
State ----------prototype------------------->State.prototype
函數(shù)對象(State {})通過prototype屬性指向它的原型對象(State.prototype

State.prototype ---------constructor----------------> State
state1---------------------constructor----------------> State
然而原型對象和實例對象都會有個一個constructor指向其構(gòu)造函數(shù)([Function: State]
state1 -----------------__proto__·-----------------> State.prototype
實例對象會通過原型鏈屬性__proto__指向其構(gòu)造函數(shù)的原型對象


5.2 構(gòu)造函數(shù)

因為State是通過new function來的所有他是一個構(gòu)造函數(shù)燕侠,而State.prototype則是該State的一個實例對象者祖,就是一個普通的函數(shù)State {},從運行可以看出

> console.log(State)
[Function: State]
> console.log(State.prototype);
State {}
> console.log(state1)
State {}

看看這個定一個匿名函數(shù)

> var fun3 = new Function();
undefined
> console.log(fun3);
[Function: anonymous]
undefined
>   

再回頭去看看上面的 function State(){} 定義函數(shù)是否理解了呢

上面說State的實例對象就是一個普通對象绢彤,怎么理解七问,
在工作中是不是常常會var obj = {};這樣來定義一個對象呢,應(yīng)該都有過吧茫舶,這就是定于了一個普通對象械巡,然而State.prototype的原型對象也是一個{}
這就和好的論證了他是一個普通對象

> var obj = {};
undefined
> console.log(obj.prototype);
undefined
undefined
> console.log(obj.__proto__);
{}
undefined
>   

這里的obj.prototype == undefined也充分證明其只是一個普通對象,只有函數(shù)對象才會有prototype

> console.log(State.prototype);
State {}
undefined
> console.log(State.prototype.__proto__);
{}

看到這個是否理解了上面說的【函數(shù)對象(State {})通過prototype屬性指向它的原型對象(State.prototype)】

5.3 構(gòu)造函數(shù)的由來

那么構(gòu)造函數(shù)最終來之哪里呢

> console.log(State.__proto__);
[Function]
undefined
>       

他的原型對象是Function饶氏;那么就會問Function的原型對象又是誰呢讥耗?(Object)? no 他是他自己疹启。葛账。。

> console.log(Function.prototype);
[Function]
undefined
>                                                                                                                             

why?
再來回顧下
function State(){}的 State.prototypeState 的一個實例那就等同于
State.prototype = new State();

以此類推
Function.prototype 也是Function 的一個實例皮仁;那么
Function.prototype = new Function();

而 上面曾經(jīng)定義匿名函數(shù)時提到使用new Function()創(chuàng)建的函數(shù)就是構(gòu)造函數(shù)
那么Function.prototype 也是通過new Function()創(chuàng)建的,那么他是不是也該是個構(gòu)造函數(shù)呢菲宴,也就是等于了他自己

那么Function的__proto__就很簡單了贷祈,
Function.__proto__ == Function.prototype == Function

那最后Function.prototype 的原型鏈對象又指向了誰,不可能還是Function吧喝峦?
那倒不是了势誊。那豈不成死循環(huán)了
所以它依然遵循了萬物法則,一切皆從無中來

> console.log(Function.prototype.__proto__);
{}
undefined

也就是說

Function.prototype.__proto__ == {}
var obj = {};
var obj2 = new Object();

使用node運行測試

> var obj2 = new Object();
undefined
> console.log(obj2.prototype);
undefined
undefined
> console.log(obj2.__proto__);
{}
undefined
>    

是否圓滿了所有對象都繼承之Object的論題谣蠢!

個人理解粟耻,有不正確之處,歡迎留言指出眉踱,共同學(xué)習(xí)一起進步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挤忙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谈喳,更是在濱河造成了極大的恐慌册烈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿禽,死亡現(xiàn)場離奇詭異赏僧,居然都是意外死亡,警方通過查閱死者的電腦和手機扭倾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門淀零,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膛壹,你說我怎么就攤上這事驾中“埃” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵哀卫,是天一觀的道長巨坊。 經(jīng)常有香客問我,道長此改,這世上最難降的妖魔是什么趾撵? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮共啃,結(jié)果婚禮上占调,老公的妹妹穿的比我還像新娘。我一直安慰自己移剪,他們只是感情好究珊,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纵苛,像睡著了一般剿涮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上攻人,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天取试,我揣著相機與錄音,去河邊找鬼怀吻。 笑死瞬浓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蓬坡。 我是一名探鬼主播猿棉,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屑咳!你這毒婦竟也來了萨赁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兆龙,失蹤者是張志新(化名)和其女友劉穎位迂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體详瑞,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡掂林,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坝橡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泻帮。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖计寇,靈堂內(nèi)的尸體忽然破棺而出锣杂,到底是詐尸還是另有隱情脂倦,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布元莫,位于F島的核電站赖阻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏踱蠢。R本人自食惡果不足惜火欧,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茎截。 院中可真熱鬧苇侵,春花似錦、人聲如沸企锌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撕攒。三九已至陡鹃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抖坪,已是汗流浹背杉适。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柳击,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓片习,卻偏偏與公主長得像捌肴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藕咏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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