眾所周知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.prototype
是State
的一個實例那就等同于
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í)一起進步