眾所周知js原型及原型鏈?zhǔn)呛芏嚅_(kāi)發(fā)者的一個(gè)疼點(diǎn)(我也不例外),我也曾多次被問(wèn)起,也問(wèn)過(guò)不少其他人,如果在自己沒(méi)有真正的去實(shí)踐和理解過(guò);那么突然之間要去用最簡(jiǎn)單的話語(yǔ)進(jìn)行概述還真不是一件容易的事情;
其實(shí)工作中看似神秘的js原型也并不是那么難以理解,最終其目的無(wú)非是為了達(dá)到方法、屬性共享代碼重用的目的;在我所了解的編程語(yǔ)言中都會(huì)用到object這個(gè)最頂層對(duì)象,然而生命的法則最終是從無(wú)到有,就如同世界先有雞還是先有蛋一樣。
一、 Class
先來(lái)看一個(gè)簡(jiǎn)單的es6的例子吧
假如我們定義一個(gè)類
/*用于全局狀態(tài)管理*/
class State{}
然后在某個(gè)頁(yè)面為其State.draw = true 然后在項(xiàng)目的任何地方都能使用State.draw來(lái)獲取其值;而且當(dāng)你一個(gè)地方更改,其它頁(yè)面也會(huì)同時(shí)獲取到更改后的值
class State{
constructor(draw){
this.draw = draw;
}
}
然而使用對(duì)象屬性方式就不一樣了;
var state1 = new State('bar');
var state2 = new State('pie');
state2.draw = 'hello'; // 不管state2 的draw 怎么改都不會(huì)影響到 state1 的屬性值
為啥扯上了這么一大圈,這個(gè)和原型好像沒(méi)關(guān)系,其實(shí)是有的,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 怎么改都不會(huì)影響到 state1 的屬性值
使用node運(yùn)行測(cè)試
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
>
柑橘和第一個(gè)Class案例是有點(diǎn)相同了,在題中state1 和 state2 完全是兩個(gè)不同的對(duì)象,他們各自維護(hù)自己的屬性和方法和其他人沒(méi)得關(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運(yùn)行測(cè)試
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 是一個(gè)空的 State {} 那上面卻能正常打印draw
State {}
undefined
> console.log(state2);
State {}
undefined
>
看:打印的 state1和 state2 是一個(gè)空的 State {} 那上面卻能正常打印draw;其實(shí)這里的state1 和state2 只是State 的一個(gè)引用,在實(shí)例本身是沒(méi)有任何屬性的,但是他可以通過(guò)自身的__proto__關(guān)聯(lián)到Sate這個(gè)構(gòu)造函數(shù)
> console.log(state1.__proto__);
State { draw: 'pie' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie' }
undefined
>
而注意的是這個(gè)屬性并沒(méi)有直接關(guān)聯(lián)構(gòu)造函數(shù);只是關(guān)聯(lián)了構(gòu)造函數(shù)的prototype屬性(原型對(duì)象)
> 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
>
四、原型的用途
然而說(shuō)了半天,這個(gè)原型的用處究竟在哪里呢?
其實(shí)他的主要用途就是 繼承(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)在就可以開(kāi)始使用了
var state1 = new State();
var state2 = new State();
var state3 = new State();
var state4 = new State();
// 使用實(shí)例化的四個(gè)實(shí)例,調(diào)用drawGraphic進(jìn)行圖形繪制
state1.drawGraphic();
state2.drawGraphic();
state3.drawGraphic();
state4.drawGraphic();
如上:實(shí)例化的四個(gè)實(shí)例,調(diào)用drawGraphic進(jìn)行圖形繪制,
然而他們并沒(méi)有去創(chuàng)建各自的方法,只是直接從原型引用了State 上的drawGraphic,這樣就極大的節(jié)約了開(kāi)銷;
如果不使用原型的方式,這個(gè)四個(gè)對(duì)象將會(huì)創(chuàng)建四個(gè)對(duì)應(yīng)的方法,這就是一種極大浪費(fèi)。
如果不明白可以來(lái)看看看開(kāi)始的例子
4.1 普通function, 實(shí)例化多個(gè)對(duì)象
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
>
從運(yùn)行可以看出每個(gè)new出來(lái)的實(shí)例都會(huì)創(chuàng)建屬于自己的實(shí)例方法和屬性
4.2 使用原型方式
只用從State 上面調(diào)用drawGraphic 方法,而不會(huì)自己再去創(chuàng)建,就同java 的繼承一個(gè)意思,直接從父類繼承方法,屬性,然后使用??矗?/p>
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 實(shí)例原型重寫(xiě)
那有的童鞋就會(huì)問(wèn)了你這個(gè)只能調(diào)一個(gè)方法打印同樣的圖形,太死板了;其實(shí)不然,原型也支持重寫(xiě)(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運(yùn)行測(cè)試
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
>
看運(yùn)行結(jié)果:state2將 draw 重新設(shè)置為了 circle ;再次調(diào)用打印 Draw a circle with a height of 100px and a width of 100px;然而他并沒(méi)有影響到其他的實(shí)例(其實(shí)說(shuō)白了就是在執(zhí)行state2.draw = 'circle'; 在state2實(shí)例對(duì)象上新增了一個(gè)draw的屬性)
為了能更好理解繼承和重寫(xiě):來(lái)用我們小學(xué)老師教我的語(yǔ)文解釋一哈【
繼承如同你父親開(kāi)了一家xxx公司,你就可以直接找財(cái)務(wù)開(kāi)個(gè)20萬(wàn),今天要去約個(gè)女朋友吃飯,然后財(cái)務(wù)一看原來(lái)是少爺呀,大筆一揮給你了,然后你再開(kāi)上你父親的法拉利愉快的約會(huì)去了。
當(dāng)某一天你發(fā)現(xiàn)自己也該干一番事業(yè)了,于是開(kāi)始模仿你父親建起來(lái)了同樣的公司和相同的經(jīng)驗(yàn)?zāi)J?,也許有人會(huì)想干嘛不直接把父親公司改為自己的?
那肯定不行啦,因?yàn)槟菢永洗?,姥二,老三……不?huì)把你打死呀。最后只好自己模擬了個(gè)和你父親相同的xxx子公司,現(xiàn)在出去約會(huì)就直接叫自己財(cái)務(wù)開(kāi)單了,然后開(kāi)著自己的法拉利愉快的且。
假如哪天經(jīng)驗(yàn)不當(dāng)(女朋友太多,哈哈),又得去找你父親的財(cái)務(wù)了,那就不一樣了現(xiàn)在得明確指定是去父親的財(cái)務(wù)那里(super.財(cái)務(wù))還是自己的財(cái)務(wù)那里開(kāi)支票;
否則你直接給你手下說(shuō)去叫財(cái)務(wù)給我20萬(wàn),他第一反應(yīng)當(dāng)然是去找你自己公司的財(cái)務(wù)了,啊哈哈?!?/p>
C:\Users\Lenovo>node
> console.log(state2);
State { draw: 'circle' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie', drawGraphic: [Function] }
undefined
>
如果上面啥子重寫(xiě)依然搞不清楚那可以把它看成(雖然不是太準(zhǔn)確,為了理解還是可以的)
在執(zhí)行state2.draw = 'circle';是為該state2實(shí)例對(duì)象新增了一個(gè)draw的屬性,
那你可能會(huì)迷惑了打印是為啥會(huì)是circle 而不是pie呢,不是說(shuō)好的state2 的原型指向State.prototype嗎,
State中的draw并沒(méi)有改變呀,其實(shí)問(wèn)題在于js 屬性查找機(jī)制(就近原則)
首先獲取屬性值會(huì)優(yōu)先從自己對(duì)象上面查找,當(dāng)對(duì)象沒(méi)有該屬性才會(huì)通過(guò)__proto__到原型對(duì)象上面去找那個(gè)屬性;
假如該State.prototype原型對(duì)象上也沒(méi)有該屬性,他會(huì)再根據(jù)State.prototype.__proto__繼續(xù)向上,直到Object;直達(dá)Object.protopyte.__proto__,最后如果還沒(méi)有找到對(duì)于的屬性;那就給你個(gè)undefined了
4.4 原型對(duì)象重寫(xiě)
那你說(shuō)假如我就是想更改所有對(duì)象的draw咋辦呢。當(dāng)然有辦法(你想翻天誰(shuí)都攔不住,,哈哈)
> 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沒(méi)有遭更改因?yàn)樽铋_(kāi)始他就重寫(xiě)了一次(他自己有了自己的子公司)
五、 原型鏈
說(shuō)了半天沒(méi)講到原型鏈,不地道,其實(shí)上面都已經(jīng)出現(xiàn)過(guò)n次了,只是你沒(méi)注意(世上最遙遠(yuǎn)的距離是:我就在你眼前你卻不認(rèn)識(shí))哈哈扯遠(yuǎn)了。
想想一個(gè)實(shí)例對(duì)象和函數(shù)對(duì)象是怎么取得聯(lián)系的,不就是通過(guò)__proto__這個(gè)屬性(這個(gè)屬性會(huì)在函數(shù)對(duì)象或是普通對(duì)象一降生就會(huì)自帶而來(lái))嗎?
那就對(duì)了其實(shí)他作用就是建立實(shí)例與函數(shù)對(duì)象之間的一條鏈子簡(jiǎn)稱原型鏈
注意這個(gè)__proto__ 是所有人都有(一視同仁);然而prototype這就就不一樣了只有函數(shù)對(duì)象才具備(只有大佬才具備,哈哈)
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ù)對(duì)象
現(xiàn)在來(lái)揭開(kāi)原型鏈的神秘面紗了
接著看
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的一個(gè)實(shí)例
佐證一下:每一個(gè)實(shí)例對(duì)象都會(huì)默認(rèn)攜帶一個(gè)constructor屬性指向其構(gòu)造函數(shù),那么原型對(duì)象為什么也會(huì)有一個(gè)constructor屬性呢,其實(shí)他也是構(gòu)造函數(shù)的一個(gè)實(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和他的實(shí)例之間的愛(ài)恨情仇算是基本清楚了吧
不清楚再簡(jiǎn)單畫(huà)一哈:
State ----------prototype------------------->State.prototype
函數(shù)對(duì)象(State {})通過(guò)prototype屬性指向它的原型對(duì)象(State.prototype)
State.prototype ---------constructor----------------> State
state1---------------------constructor----------------> State
然而原型對(duì)象和實(shí)例對(duì)象都會(huì)有個(gè)一個(gè)constructor指向其構(gòu)造函數(shù)([Function: State])
state1 -----------------__proto__·-----------------> State.prototype
實(shí)例對(duì)象會(huì)通過(guò)原型鏈屬性__proto__指向其構(gòu)造函數(shù)的原型對(duì)象
5.2 構(gòu)造函數(shù)
因?yàn)?code>State是通過(guò)new function來(lái)的所有他是一個(gè)構(gòu)造函數(shù),而State.prototype則是該State的一個(gè)實(shí)例對(duì)象,就是一個(gè)普通的函數(shù)State {},從運(yùn)行可以看出
> console.log(State)
[Function: State]
> console.log(State.prototype);
State {}
> console.log(state1)
State {}
看看這個(gè)定一個(gè)匿名函數(shù)
> var fun3 = new Function();
undefined
> console.log(fun3);
[Function: anonymous]
undefined
>
再回頭去看看上面的 function State(){} 定義函數(shù)是否理解了呢
上面說(shuō)State的實(shí)例對(duì)象就是一個(gè)普通對(duì)象,怎么理解,
在工作中是不是常常會(huì)var obj = {};這樣來(lái)定義一個(gè)對(duì)象呢,應(yīng)該都有過(guò)吧,這就是定于了一個(gè)普通對(duì)象,然而State.prototype的原型對(duì)象也是一個(gè){}
這就和好的論證了他是一個(gè)普通對(duì)象
> var obj = {};
undefined
> console.log(obj.prototype);
undefined
undefined
> console.log(obj.__proto__);
{}
undefined
>
這里的obj.prototype == undefined也充分證明其只是一個(gè)普通對(duì)象,只有函數(shù)對(duì)象才會(huì)有prototype
> console.log(State.prototype);
State {}
undefined
> console.log(State.prototype.__proto__);
{}
看到這個(gè)是否理解了上面說(shuō)的【函數(shù)對(duì)象(State {})通過(guò)prototype屬性指向它的原型對(duì)象(State.prototype)】
5.3 構(gòu)造函數(shù)的由來(lái)
那么構(gòu)造函數(shù)最終來(lái)之哪里呢
> console.log(State.__proto__);
[Function]
undefined
>
他的原型對(duì)象是Function;那么就會(huì)問(wèn)Function的原型對(duì)象又是誰(shuí)呢?(Object)? no 他是他自己。。。
> console.log(Function.prototype);
[Function]
undefined
>
why?
再來(lái)回顧下
function State(){}的 State.prototype是State 的一個(gè)實(shí)例那就等同于
State.prototype = new State();
以此類推
Function.prototype 也是Function 的一個(gè)實(shí)例;那么
Function.prototype = new Function();
而 上面曾經(jīng)定義匿名函數(shù)時(shí)提到使用new Function()創(chuàng)建的函數(shù)就是構(gòu)造函數(shù)
那么Function.prototype 也是通過(guò)new Function()創(chuàng)建的,那么他是不是也該是個(gè)構(gòu)造函數(shù)呢,也就是等于了他自己
那么Function的__proto__就很簡(jiǎn)單了,
Function.__proto__ == Function.prototype == Function
那最后Function.prototype 的原型鏈對(duì)象又指向了誰(shuí),不可能還是Function吧?
那倒不是了。那豈不成死循環(huán)了
所以它依然遵循了萬(wàn)物法則,一切皆從無(wú)中來(lái)
> console.log(Function.prototype.__proto__);
{}
undefined
也就是說(shuō)
Function.prototype.__proto__ == {}
var obj = {};
var obj2 = new Object();
使用node運(yùn)行測(cè)試
> var obj2 = new Object();
undefined
> console.log(obj2.prototype);
undefined
undefined
> console.log(obj2.__proto__);
{}
undefined
>
是否圓滿了所有對(duì)象都繼承之Object的論題!
個(gè)人理解,有不正確之處,歡迎留言指出,共同學(xué)習(xí)一起進(jìn)步